From 3d009565628093bcc58a614ce99df456e8bfab1a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 21 Jan 2025 16:04:47 -0600 Subject: [PATCH 01/60] BotStateSet with simplified BotState API --- .../DialogExtensions.cs | 2 +- .../AutoSaveStateMiddleware.cs | 22 ++ .../Core/Microsoft.Agents.State/BotState.cs | 188 ++++++++---------- .../Microsoft.Agents.State/BotStateSet.cs | 51 +++-- .../ConversationState.cs | 10 +- .../Core/Microsoft.Agents.State/ObjectPath.cs | 90 ++++++--- .../PrivateConversationState.cs | 11 +- .../Core/Microsoft.Agents.State/UserState.cs | 14 +- .../Teams/bot-all-cards/Bots/DialogBot.cs | 6 + src/samples/Teams/bot-all-cards/Program.cs | 4 +- .../Bots/DialogBot.cs | 6 + .../Program.cs | 4 +- .../Program.cs | 4 +- .../Bots/DialogBot.cs | 6 + .../Teams/bot-teams-authentication/Program.cs | 4 +- .../BotBuilder.Testing/DialogTestClient.cs | 7 +- .../ActivityPromptTests.cs | 15 +- .../AttachmentPromptTests.cs | 6 +- .../ChoicePromptTests.cs | 48 +++-- .../CloudOAuthPromptTests.cs | 24 ++- .../ComponentDialogTests.cs | 18 +- .../ConfirmPromptLocTests.cs | 6 +- .../ConfirmPromptTests.cs | 27 ++- .../DateTimePromptTests.cs | 9 +- .../DialogExtensionsTests.cs | 3 + .../NumberPromptTests.cs | 48 +++-- .../OAuthPromptTests.cs | 68 +++++-- .../PromptValidatorContextTests.cs | 9 +- .../ReplaceDialogTest.cs | 3 + .../TextPromptTests.cs | 15 +- .../WaterfallTests.cs | 21 +- .../Bots/DialogBot.cs | 3 + .../DialogExtensions.cs | 2 +- .../Dialogs/UserProfileDialog.cs | 2 +- .../AutoSaveStateMiddlewareTests.cs | 41 ++-- .../BotStateSetTests.cs | 176 ++++++++++++---- .../BotStateTests.cs | 185 ++++++++++------- .../ObjectPathTests.cs | 2 +- 38 files changed, 758 insertions(+), 402 deletions(-) rename src/tests/{Microsoft.Agents.BotBuilder.Tests => Microsoft.Agents.State.Tests}/AutoSaveStateMiddlewareTests.cs (80%) rename src/tests/{Microsoft.Agents.BotBuilder.Dialogs.Tests => Microsoft.Agents.State.Tests}/ObjectPathTests.cs (99%) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index ff0d30e7..e7055570 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -37,7 +37,7 @@ public static class DialogExtensions /// A representing the asynchronous operation. public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, BotState state, CancellationToken cancellationToken) { - var dialogState = await state.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + var dialogState = state.GetValue("DialogState", () => new DialogState()); var dialogSet = new DialogSet(dialogState); // look for the IBotTelemetryClient on the TurnState, if not there take it from the Dialog, if not there fall back to the "null" default diff --git a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs b/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs index f467c30b..0123dfbf 100644 --- a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs +++ b/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs @@ -17,12 +17,26 @@ namespace Microsoft.Agents.State /// public class AutoSaveStateMiddleware : IMiddleware { + private readonly bool _autoLoad; + /// /// Initializes a new instance of the class. /// /// initial list of objects to manage. public AutoSaveStateMiddleware(params BotState[] botStates) { + _autoLoad = false; + BotStateSet = new BotStateSet(botStates); + } + + /// + /// Allows for optionally auto-loading BotState at turn start. + /// + /// + /// + public AutoSaveStateMiddleware(bool autoLoad, params BotState[] botStates) + { + _autoLoad = autoLoad; BotStateSet = new BotStateSet(botStates); } @@ -67,7 +81,15 @@ public AutoSaveStateMiddleware Add(BotState botState) /// This middleware persists state after the bot logic completes and before the turn ends. public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) { + // before turn + if (_autoLoad) + { + await BotStateSet.LoadAllAsync(turnContext, true, cancellationToken).ConfigureAwait(false); + } + await next(cancellationToken).ConfigureAwait(false); + + // after turn await BotStateSet.SaveAllChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false); } } diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs index a70cdb66..c73e5721 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotState.cs @@ -31,6 +31,7 @@ public abstract class BotState : IPropertyManager { private readonly string _contextServiceKey; private readonly IStorage _storage; + private CachedBotState _cachedBotState; /// /// Initializes a new instance of the class. @@ -71,43 +72,45 @@ public IStatePropertyAccessor CreateProperty(string name) /// /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// - /// The turn context. /// value. - /// The cancellation token. /// A representing the asynchronous operation. - public async Task DeletePropertyAsync(ITurnContext turnContext, string name, CancellationToken cancellationToken) + public void DeleteValue(string name) { - await LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - await DeletePropertyValueAsync(turnContext, name, cancellationToken).ConfigureAwait(false); + if (!IsLoaded()) + { + throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + } + + DeletePropertyValue(name); } /// /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// - /// The context object for this turn. /// value. /// Defines the default value. /// Invoked when no value been set for the requested state property. /// If defaultValueFactory is defined as null in that case, the method returns null and - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task GetPropertyAsync(ITurnContext turnContext, string name, Func defaultValueFactory, CancellationToken cancellationToken) + public T GetValue(string name, Func defaultValueFactory = null) { - T result = default; + if (!IsLoaded()) + { + throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + } - await LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + T result = default; try { // if T is a value type, lookup up will throw key not found if not found, but as perf // optimization it will return null if not found for types which are not value types (string and object). - result = await GetPropertyValueAsync(turnContext, name, cancellationToken).ConfigureAwait(false); + result = GetPropertyValue(name); if (result == null && defaultValueFactory != null) { // use default Value Factory and save default value for any further calls result = defaultValueFactory(); - await SetPropertyAsync(turnContext, name, result, cancellationToken).ConfigureAwait(false); + SetValue(name, result); } } catch (KeyNotFoundException) @@ -116,7 +119,7 @@ public async Task GetPropertyAsync(ITurnContext turnContext, string name, { // use default Value Factory and save default value for any further calls result = defaultValueFactory(); - await SetPropertyAsync(turnContext, name, result, cancellationToken).ConfigureAwait(false); + SetValue(name, result); } } @@ -126,21 +129,34 @@ public async Task GetPropertyAsync(ITurnContext turnContext, string name, /// /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// - /// turn context. /// value. /// value. - /// The cancellation token. /// A representing the asynchronous operation. - public async Task SetPropertyAsync(ITurnContext turnContext, string name, T value, CancellationToken cancellationToken) + public void SetValue(string name, T value) { - await LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - await SetPropertyValueAsync(turnContext, name, value, cancellationToken).ConfigureAwait(false); + if (!IsLoaded()) + { + throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + } + + SetPropertyValue(name, value); } + /// + /// True if state has been loaded. + /// + /// + public bool IsLoaded() + { + return _cachedBotState != null; + } /// /// Populates the state cache for this from the storage layer. /// + /// + /// LoadAsync loads State for the specified turn. + /// /// The context object for this turn. /// Optional, true to overwrite any existing state cache; /// or false to load state from storage only if the cache doesn't already exist. @@ -152,26 +168,26 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false { ArgumentNullException.ThrowIfNull(turnContext); - var cachedState = GetCachedState(turnContext); var storageKey = GetStorageKey(turnContext); - if (force || cachedState == null || cachedState.State == null) + + if (ShouldLoad(storageKey, force)) { var items = await _storage.ReadAsync([storageKey], cancellationToken).ConfigureAwait(false); items.TryGetValue(storageKey, out object val); if (val is IDictionary asDictionary) { - turnContext.TurnState[_contextServiceKey] = new CachedBotState(asDictionary); + _cachedBotState = new CachedBotState(storageKey, asDictionary); } else if (val is JsonObject || val is JsonElement) { // If types are not used by storage serialization, try deserializing to object - turnContext.TurnState[_contextServiceKey] = new CachedBotState(ProtocolJsonSerializer.ToObject>(val)); + _cachedBotState = new CachedBotState(storageKey, ProtocolJsonSerializer.ToObject>(val)); } else if (val == null) { // This is the case where the dictionary did not exist in the store. - turnContext.TurnState[_contextServiceKey] = new CachedBotState(); + _cachedBotState = new CachedBotState(storageKey); } else { @@ -181,6 +197,16 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false } } + private bool ShouldLoad(string storageKey, bool force) + { + if (_cachedBotState != null && _cachedBotState.Key != storageKey) + { + throw new InvalidOperationException($"BotState '{GetType().Name}' is being used by multiple conversations. Verify \"AddTransient\" DI registration."); + } + + return force || _cachedBotState == null || _cachedBotState.State == null; + } + /// /// Writes the state cache for this to the storage layer. /// @@ -195,7 +221,7 @@ public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force { ArgumentNullException.ThrowIfNull(turnContext); - var cachedState = GetCachedState(turnContext); + var cachedState = GetCachedState(); if (cachedState != null && (force || cachedState.IsChanged())) { var key = GetStorageKey(turnContext); @@ -212,23 +238,20 @@ public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force /// /// Clears the state cache for this . /// - /// The context object for this turn. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. /// This method clears the state cache in the turn context. Call /// to persist this /// change in the storage layer. /// - /// is null. - public virtual Task ClearStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + public virtual void ClearState() { - ArgumentNullException.ThrowIfNull(turnContext); + if (!IsLoaded()) + { + throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + } // Explicitly setting the hash will mean IsChanged is always true. And that will force a Save. - turnContext.TurnState[_contextServiceKey] = new CachedBotState { Hash = string.Empty }; - - return Task.CompletedTask; + _cachedBotState = new CachedBotState(_cachedBotState.Key) { Hash = string.Empty }; } /// @@ -241,12 +264,9 @@ public virtual Task ClearStateAsync(ITurnContext turnContext, CancellationToken /// is null. public virtual async Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(turnContext); - - var cachedState = GetCachedState(turnContext); - if (cachedState != null) + if (_cachedBotState != null) { - turnContext.TurnState.Remove(_contextServiceKey); + ClearState(); } var storageKey = GetStorageKey(turnContext); @@ -256,14 +276,10 @@ public virtual async Task DeleteStateAsync(ITurnContext turnContext, Cancellatio /// /// Gets a copy of the raw cached data for this from the turn context. /// - /// The context object for this turn. /// A JSON representation of the cached state. - /// is null. - internal JsonElement Get(ITurnContext turnContext) + internal JsonElement Get() { - ArgumentNullException.ThrowIfNull(turnContext); - - var cachedState = GetCachedState(turnContext); + var cachedState = GetCachedState(); return JsonSerializer.SerializeToElement(cachedState.State, ProtocolJsonSerializer.SerializationOptions); } @@ -271,14 +287,10 @@ internal JsonElement Get(ITurnContext turnContext) /// Gets the cached bot state instance that wraps the raw cached data for this /// from the turn context. /// - /// The context object for this turn. /// The cached bot state instance. - /// is null. - internal CachedBotState GetCachedState(ITurnContext turnContext) + internal CachedBotState GetCachedState() { - ArgumentNullException.ThrowIfNull(turnContext); - - return turnContext.TurnState.Get(_contextServiceKey); + return _cachedBotState; } /// @@ -292,89 +304,45 @@ internal CachedBotState GetCachedState(ITurnContext turnContext) /// Gets the value of a property from the state cache for this . /// /// The value type of the property. - /// The context object for this turn. /// The name of the property. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. /// If the task is successful, the result contains the property value, otherwise it will be default(T). - /// or - /// is null. #pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) - protected Task GetPropertyValueAsync(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default) + protected T GetPropertyValue(string propertyName) #pragma warning restore CA1801 // Review unused parameters { - ArgumentNullException.ThrowIfNull(turnContext); ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - var cachedState = GetCachedState(turnContext); - - if (cachedState.State.TryGetValue(propertyName, out object result)) - { - if (result is T t) - { - return Task.FromResult(t); - } - - if (result == null) - { - return Task.FromResult(default(T)); - } - - // If types are not used by storage serialization try to convert the object to the type expected - // using the serializer. - var converted = ProtocolJsonSerializer.ToObject(result); - cachedState.State[propertyName] = converted; - return Task.FromResult(converted); - } - - if (typeof(T).IsValueType) - { - throw new KeyNotFoundException(propertyName); - } - - return Task.FromResult(default(T)); + var cachedState = GetCachedState(); + return ObjectPath.GetPathValue(cachedState.State, propertyName, true); } /// /// Deletes a property from the state cache for this . /// - /// The context object for this turn. /// The name of the property. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - /// or - /// is null. - protected Task DeletePropertyValueAsync(ITurnContext turnContext, string propertyName, CancellationToken cancellationToken = default) + protected void DeletePropertyValue(string propertyName) { - ArgumentNullException.ThrowIfNull(turnContext); ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - var cachedState = GetCachedState(turnContext); + var cachedState = GetCachedState(); cachedState.State.Remove(propertyName); - return Task.CompletedTask; } /// /// Sets the value of a property in the state cache for this . /// - /// The context object for this turn. /// The name of the property to set. /// The value to set on the property. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - /// or - /// is null. - protected Task SetPropertyValueAsync(ITurnContext turnContext, string propertyName, object value, CancellationToken cancellationToken = default) + protected void SetPropertyValue(string propertyName, object value) { - ArgumentNullException.ThrowIfNull(turnContext); ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - var cachedState = GetCachedState(turnContext); - cachedState.State[propertyName] = value; - return Task.CompletedTask; + var cachedState = GetCachedState(); + //cachedState.State[propertyName] = value; + ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); } /// @@ -385,11 +353,13 @@ internal class CachedBotState /// /// Initializes a new instance of the class. /// + /// Unique state key. Typically the storage key. /// Initial state for the . - public CachedBotState(IDictionary state = null) + public CachedBotState(string key, IDictionary state = null) { State = state ?? new Dictionary(); Hash = ComputeHash(State); + Key = key; } /// @@ -404,6 +374,8 @@ public CachedBotState(IDictionary state = null) internal string Hash { get; set; } + internal string Key { get; set; } + internal static string ComputeHash(object obj) { return ProtocolJsonSerializer.ToJson(obj); @@ -451,7 +423,7 @@ public BotStatePropertyAccessor(BotState botState, string name) public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken) { await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - await _botState.DeletePropertyValueAsync(turnContext, Name, cancellationToken).ConfigureAwait(false); + _botState.DeleteValue(Name); } /// @@ -474,7 +446,7 @@ public async Task GetAsync(ITurnContext turnContext, Func defaultValueFact { // if T is a value type, lookup up will throw key not found if not found, but as perf // optimization it will return null if not found for types which are not value types (string and object). - result = await _botState.GetPropertyValueAsync(turnContext, Name, cancellationToken).ConfigureAwait(false); + result = _botState.GetValue(Name, defaultValueFactory); if (result == null && defaultValueFactory != null) { @@ -506,7 +478,7 @@ public async Task GetAsync(ITurnContext turnContext, Func defaultValueFact public async Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken) { await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - await _botState.SetPropertyValueAsync(turnContext, Name, value, cancellationToken).ConfigureAwait(false); + _botState.SetValue(Name, value); } } #endregion diff --git a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs index eee107eb..0a9a0ffc 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs @@ -15,6 +15,8 @@ namespace Microsoft.Agents.State /// public class BotStateSet { + private IDictionary _scopes { get; set; } = new Dictionary(); + /// /// Initializes a new instance of the class. /// @@ -23,26 +25,45 @@ public BotStateSet(params BotState[] botStates) { foreach (var botState in botStates) { - BotStates.Add(botState.ContextServiceKey, botState); + _scopes.Add(botState.ContextServiceKey, botState); } } - /// - /// Gets or sets the BotStates list for the BotStateSet. - /// - /// The BotState objects managed by this class. - public IDictionary BotStates { get; set; } = new Dictionary(); + public T GetValue(ITurnContext turnContext, string name, Func defaultValueFactory) + { + var (scope, property) = GetScopeAndPath(name); + return GetScope(scope).GetValue(property, defaultValueFactory); + } + + public void SetValue(ITurnContext turnContext, string name, object value) + { + var (scope, property) = GetScopeAndPath(name); + GetScope(scope).SetValue(property, value); + } + + public BotState GetScope(string scope) + { + if (!_scopes.TryGetValue(scope, out BotState value)) + { + throw new ArgumentException($"Scope '{scope}' not found"); + } + return value; + } + + private (string, string) GetScopeAndPath(string name) + { + var scopeEnd = name.IndexOf('.'); + if (scopeEnd == -1) + { + throw new ArgumentException("Path must include the state scope name"); + } + return (name.Substring(0, scopeEnd), name.Substring(scopeEnd + 1)); + } - /// - /// Adds a bot state object to the set. - /// - /// The bot state object to add. - /// The updated , so you can fluently call multiple times. public BotStateSet Add(BotState botState) { ArgumentNullException.ThrowIfNull(botState); - - BotStates.Add(botState.ContextServiceKey, botState); + _scopes.Add(botState.ContextServiceKey, botState); return this; } @@ -56,7 +77,7 @@ public BotStateSet Add(BotState botState) /// A task that represents the work queued to execute. public async Task LoadAllAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { - var tasks = BotStates.Select(bs => bs.Value.LoadAsync(turnContext, force, cancellationToken)).ToList(); + var tasks = _scopes.Select(bs => bs.Value.LoadAsync(turnContext, force, cancellationToken)).ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -70,7 +91,7 @@ public async Task LoadAllAsync(ITurnContext turnContext, bool force = false, Can /// A task that represents the work queued to execute. public async Task SaveAllChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { - var tasks = BotStates.Select(bs => bs.Value.SaveChangesAsync(turnContext, force, cancellationToken)).ToList(); + var tasks = _scopes.Select(kv => kv.Value.SaveChangesAsync(turnContext, force, cancellationToken)).ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); } } diff --git a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs index cb34e1d4..84f3efd1 100644 --- a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs @@ -8,17 +8,17 @@ namespace Microsoft.Agents.State { /// - /// Defines a state management object for conversation state. + /// Defines a state keyed to a conversation. /// /// /// Conversation state is available in any turn in a specific conversation, regardless of user, /// such as in a group conversation. - /// - /// - /// Initializes a new instance of the class. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. /// /// The storage layer to use. - public class ConversationState(IStorage storage) : BotState(storage, nameof(ConversationState)) + public class ConversationState(IStorage storage) : BotState(storage, "conversation") { /// /// Gets the key to use when reading and writing state to and from storage. diff --git a/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs b/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs index e058c6db..775775cf 100644 --- a/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs +++ b/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs @@ -43,10 +43,11 @@ public static bool HasValue(object obj, string path) /// type to return. /// object to start with. /// path to evaluate. + /// Set path value with result if true. /// value or default(T). - public static T GetPathValue(object obj, string path) + public static T GetPathValue(object obj, string path, bool set = false) { - if (TryGetPathValue(obj, path, out var value)) + if (TryGetPathValue(obj, path, out var value, set)) { return value; } @@ -61,10 +62,11 @@ public static T GetPathValue(object obj, string path) /// object to start with. /// path to evaluate. /// default value to use if any part of the path is missing. + /// Set path value with result if true. /// value or default(T). - public static T GetPathValue(object obj, string path, T defaultValue) + public static T GetPathValue(object obj, string path, T defaultValue, bool set = false) { - if (TryGetPathValue(obj, path, out var value)) + if (TryGetPathValue(obj, path, out var value, set)) { return value; } @@ -79,8 +81,9 @@ public static T GetPathValue(object obj, string path, T defaultValue) /// object to start with. /// path to evaluate. /// value for the path. + /// If the value was converted, set the path value with the converted value. /// true if successful. - public static bool TryGetPathValue(object obj, string path, out T value) + public static bool TryGetPathValue(object obj, string path, out T value, bool convertedSet = false) { value = default; @@ -124,6 +127,10 @@ public static bool TryGetPathValue(object obj, string path, out T value) try { value = MapValueTo(result); + if (convertedSet && value.GetType() != result.GetType()) + { + SetPathValueInner(segments, obj, value, false); + } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception) @@ -149,6 +156,11 @@ public static void SetPathValue(object obj, string path, object value, bool json return; } + SetPathValueInner(segments, obj, value, json); + } + + private static void SetPathValueInner(List segments, object obj, object value, bool json = true) + { dynamic current = obj; for (var i = 0; i < segments.Count - 1; i++) { @@ -173,21 +185,21 @@ public static void SetPathValue(object obj, string path, object value, bool json } else { - var ssegment = segment as string; - next = GetObjectProperty(current, ssegment); + var strSegment = segment as string; + next = GetObjectProperty(current, strSegment); if (next == null) { // Create object or array base on next segment var nextSegment = segments[i + 1]; - if (nextSegment is string snext) + if (nextSegment is string) { - SetObjectSegment(current, ssegment, new JsonObject()); - next = GetObjectProperty(current, ssegment); + SetObjectSegment(current, strSegment, new JsonObject(), json); + next = GetObjectProperty(current, strSegment); } else { - SetObjectSegment(current, ssegment, new JsonArray()); - next = GetObjectProperty(current, ssegment); + SetObjectSegment(current, strSegment, new JsonArray(), json); + next = GetObjectProperty(current, strSegment); } } } @@ -430,7 +442,8 @@ public static T MapValueTo(object val) { if (val is JsonElement) { - return JsonSerializer.Deserialize(JsonSerializer.Serialize(val)); + T result = ProtocolJsonSerializer.ToObject(val); + return result; } if (typeof(T) == typeof(object)) @@ -468,9 +481,9 @@ public static T MapValueTo(object val) return JsonSerializer.Deserialize(JsonSerializer.Serialize(val, _serializerOptions), _serializerOptions); } - if (val is T) + if (val is T t) { - return (T)val; + return t; } if (val is short || val is int || val is long || @@ -621,7 +634,7 @@ private static bool ResolveSegment(ref dynamic current, object segment) return false; } - private static bool ResolveSegments(dynamic current, List segments, out dynamic result) + private static bool ResolveSegments(dynamic current, List segments, out object result) { result = current; foreach (var segment in segments) @@ -696,13 +709,39 @@ private static void SetObjectSegment(object obj, object segment, object value, b val = GetNormalizedValue(value, json); if (segment is int index) { - var jar = obj as JsonArray; - for (var i = jar.Count; i <= index; i++) + if (obj is JsonArray jarray) + { + // grow array if required + var jar = obj as JsonArray; + for (var i = jar.Count; i <= index; i++) + { + jar.Add(null); + } + + jar[index] = JsonSerializer.SerializeToNode(val); + } + else if (obj is Array array) { - jar.Add(null); + if (index >= array.Length) + { + // TODO + throw new ArgumentException("Cannot grow arrays yet"); + } + + array.SetValue(value, index); } + else if (obj is IList list) + { + if (index >= list.Count) + { + for (var i = list.Count; i <= index; i++) + { + list.Add(null); + } + } - jar[index] = JsonSerializer.SerializeToNode(val); + list[index] = value; + } return; } @@ -711,7 +750,7 @@ private static void SetObjectSegment(object obj, object segment, object value, b { // For case insensitive key var key = dict.Keys.Where(k => string.Equals(k, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? property; - dict[key] = val; + dict[key] = value; return; } @@ -726,7 +765,10 @@ private static void SetObjectSegment(object obj, object segment, object value, b var prop = obj.GetType().GetProperty(property); if (prop != null) { - prop.SetValue(obj, val); + if (prop.GetValue(obj) != value) + { + prop.SetValue(obj, value); + } } } @@ -741,9 +783,9 @@ private static object GetNormalizedValue(object value, bool json) object val; if (json) { - if (value is JsonNode) + if (value is JsonNode node) { - val = ((JsonNode)value).DeepClone(); + val = node.DeepClone(); } else if (value is JsonElement) { diff --git a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs index 958cae0a..f98e6ecb 100644 --- a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs @@ -8,13 +8,14 @@ namespace Microsoft.Agents.State { /// - /// Defines a state management object for private conversation state. + /// Defines a state keyed to a conversation and user. /// /// - /// Private conversation state is scoped to both the specific conversation and to that specific user. - /// - /// - /// Initializes a new instance of the class. + /// Conversation state is available in any turn in a specific conversation, regardless of user, + /// such as in a group conversation. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. /// /// The storage layer to use. public class PrivateConversationState(IStorage storage) : BotState(storage, nameof(PrivateConversationState)) diff --git a/src/libraries/Core/Microsoft.Agents.State/UserState.cs b/src/libraries/Core/Microsoft.Agents.State/UserState.cs index 3af00ffa..34546685 100644 --- a/src/libraries/Core/Microsoft.Agents.State/UserState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/UserState.cs @@ -8,17 +8,17 @@ namespace Microsoft.Agents.State { /// - /// Defines a state management object for user state. + /// Defines a state keyed to a user. /// /// - /// User state is available in any turn that the bot is conversing with that user on that - /// channel, regardless of the conversation. - /// - /// - /// Initializes a new instance of the class. + /// Conversation state is available in any turn in a specific conversation, regardless of user, + /// such as in a group conversation. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. /// /// The storage layer to use. - public class UserState(IStorage storage) : BotState(storage, nameof(UserState)) + public class UserState(IStorage storage) : BotState(storage, "user") { /// /// Gets the key to use when reading and writing state to and from storage. diff --git a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs index 66acb9ba..5a2f58aa 100644 --- a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs @@ -42,6 +42,12 @@ protected override async Task OnMessageActivityAsync(ITurnContext(); // Create the Conversation state. (Used by the Dialog system itself.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); // The Dialog that will be run by the bot. -builder.Services.AddSingleton(); +builder.Services.AddTransient(); var app = builder.Build(); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs index 44755349..c0c194d8 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs @@ -43,6 +43,12 @@ protected override async Task OnMessageActivityAsync( await _dialog.RunAsync(turnContext, _conversationState, cancellationToken); } + protected override async Task OnTurnBeginAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + await _conversationState.LoadAsync(turnContext, false, cancellationToken); + await _userState.LoadAsync(turnContext, false, cancellationToken); + } + protected override async Task OnTurnEndAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { // Save any state changes that might have occurred during the turn. diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index 497382f2..80c2a83a 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -31,10 +31,10 @@ builder.Services.AddSingleton(); // Create the User state. (Used in this bot's Dialog implementation.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); // Create the Conversation state. (Used by the Dialog system itself.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); // The Dialog that will be run by the bot. builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs index b9e44085..8af85cb0 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs @@ -29,10 +29,10 @@ builder.Services.AddSingleton(); // Create the User state. (Used in this bot's Dialog implementation.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); // Create the Conversation state. (Used by the Dialog system itself.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); var app = builder.Build(); diff --git a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs index 7424047c..e2d31ca9 100644 --- a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs @@ -33,6 +33,12 @@ protected override async Task OnMessageActivityAsync(ITurnContext(); +builder.Services.AddTransient(); // Create the Conversation state. (Used by the Dialog system itself.) -builder.Services.AddSingleton(); +builder.Services.AddTransient(); // The Dialog that will be run by the bot. builder.Services.AddSingleton(); diff --git a/src/tests/BotBuilder.Testing/DialogTestClient.cs b/src/tests/BotBuilder.Testing/DialogTestClient.cs index f600a420..cd2fcf2e 100644 --- a/src/tests/BotBuilder.Testing/DialogTestClient.cs +++ b/src/tests/BotBuilder.Testing/DialogTestClient.cs @@ -37,7 +37,7 @@ public DialogTestClient(string channelId, Dialog targetDialog, object initialDia { ConversationState = conversationState ?? new ConversationState(new MemoryStorage()); _testAdapter = new TestAdapter(channelId) - .Use(new AutoSaveStateMiddleware(ConversationState)); + .Use(new AutoSaveStateMiddleware(true, ConversationState)); AddUserMiddlewares(middlewares); @@ -55,7 +55,7 @@ public DialogTestClient(string channelId, Dialog targetDialog, object initialDia public DialogTestClient(TestAdapter testAdapter, Dialog targetDialog, object initialDialogOptions = null, IEnumerable middlewares = null, ConversationState conversationState = null) { ConversationState = conversationState ?? new ConversationState(new MemoryStorage()); - _testAdapter = testAdapter.Use(new AutoSaveStateMiddleware(ConversationState)); + _testAdapter = testAdapter.Use(new AutoSaveStateMiddleware(true, ConversationState)); AddUserMiddlewares(middlewares); @@ -128,7 +128,8 @@ private BotCallbackHandler GetDefaultCallback(Dialog targetDialog, object initia async (turnContext, cancellationToken) => { // Ensure dialog state is created and pass it to DialogSet. - var dialogState = await ConversationState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken).ConfigureAwait(false); + await ConversationState.LoadAsync(turnContext).ConfigureAwait(false); + var dialogState = ConversationState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(targetDialog); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs index f877879d..140d0d92 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs @@ -49,7 +49,8 @@ public async Task BasicActivityPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(eventPrompt); @@ -93,7 +94,8 @@ public async Task ActivityPromptShouldSendRetryPromptIfValidationFailed() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(eventPrompt); @@ -146,7 +148,8 @@ public async Task ActivityPromptResumeDialogShouldPromptNotRetry() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(eventPrompt); @@ -214,7 +217,8 @@ public async Task OnPromptOverloadWithoutIsRetryParamReturnsBasicActivityPrompt( await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(eventPrompt); @@ -267,7 +271,8 @@ await Assert.ThrowsAsync(async () => await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(eventPrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs index 33790bca..ed207126 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs @@ -48,7 +48,8 @@ public async Task BasicAttachmentPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(attachmentPrompt); @@ -91,7 +92,8 @@ public async Task RetryAttachmentPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new AttachmentPrompt("AttachmentPrompt")); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs index 6e8d1fa5..a9444942 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs @@ -84,7 +84,8 @@ public async Task ChoicePromptWithCardActionAndNoValueShouldNotFail() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: Culture.English)); @@ -128,7 +129,8 @@ public async Task ShouldSendPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: Culture.English)); @@ -162,7 +164,8 @@ public async Task ShouldSendPromptAsAnInlineList() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: Culture.English)); @@ -202,7 +205,8 @@ public async Task ShouldSendPromptAsANumberedList() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -241,7 +245,8 @@ public async Task ShouldSendPromptUsingSuggestedActions() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -290,7 +295,8 @@ public async Task ShouldSendPromptUsingHeroCard() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -340,7 +346,8 @@ public async Task ShouldSendPromptUsingAppendedHeroCard() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -393,7 +400,8 @@ public async Task ShouldSendPromptWithoutAddingAList() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -432,7 +440,8 @@ public async Task ShouldSendPromptWithoutAddingAListButAddingSsml() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -476,7 +485,8 @@ public async Task ShouldRecognizeAChoice() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -522,7 +532,8 @@ public async Task ShouldNotRecognizeOtherText() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -570,7 +581,8 @@ public async Task ShouldCallCustomValidator() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(listPrompt); @@ -606,7 +618,8 @@ public async Task ShouldUseChoiceStyleIfPresent() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: Culture.English) { Style = ListStyle.HeroCard }); @@ -655,7 +668,8 @@ public async Task ShouldRecognizeLocaleVariationsOfCorrectLocales(string testCul await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: testCulture)); @@ -705,7 +719,8 @@ public async Task ShouldDefaultToEnglishLocale(string activityLocale) await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", defaultLocale: activityLocale)); @@ -767,7 +782,8 @@ public async Task ShouldAcceptAndRecognizeCustomLocaleDict() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ChoicePrompt("ChoicePrompt", customDict, null, culture.Locale)); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs index efcb9cdb..bbafe5a4 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs @@ -63,7 +63,8 @@ public async Task OAuthPromptBeginLoggedIn() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -135,7 +136,8 @@ public async Task OAuthPromptBeginNotLoggedIn_OAuthCard() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -217,7 +219,8 @@ public async Task OAuthPromptBeginNotLoggedIn_SignInCard() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -288,7 +291,8 @@ public async Task OAuthPromptContinueWithTimeout() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -358,7 +362,8 @@ public async Task OAuthPromptContinueWithMessage() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -436,7 +441,8 @@ public async Task OAuthPromptContinueWithEvent() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -529,7 +535,8 @@ public async Task OAuthPromptContinueWithInvokeVerifyState() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); @@ -614,7 +621,8 @@ public async Task OAuthPromptContinueWithInvokeTokenExchange() DialogTurnResult dialogTurnResult = null; BotCallbackHandler callback = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs index d22c7a5c..eb84298e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs @@ -32,7 +32,8 @@ public async Task CallDialogInParentComponent() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); var childComponent = new ComponentDialog("childComponent"); @@ -96,7 +97,8 @@ public async Task BasicWaterfallTest() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); dialogs.Add(CreateWaterfall()); @@ -202,7 +204,8 @@ public async Task BasicComponentDialogTest() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); dialogs.Add(new TestComponentDialog()); @@ -241,7 +244,8 @@ public async Task NestedComponentDialogTest() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); dialogs.Add(new TestNestedComponentDialog()); @@ -333,7 +337,8 @@ public async Task CallDialogDefinedInParentComponent() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); dialogs.Add(parentComponent); @@ -366,7 +371,8 @@ private static TestFlow CreateTestFlow(WaterfallDialog waterfallDialog) var testFlow = new TestFlow(adapter, async (turnContext, cancellationToken) => { - var state = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var state = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(state); dialogs.Add(new CancelledComponentDialog(waterfallDialog)); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs index 7e348f90..582c0da4 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs @@ -226,7 +226,8 @@ public async Task ConfirmPrompt_Locale_Override_ChoiceDefaults(string defaultLoc { turnContext.Activity.Locale = culture.Locale; - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); // Prompt should default to English if locale is a non-supported value @@ -269,7 +270,8 @@ private async Task ConfirmPrompt_Locale_Impl(string activityLocale, string defau { turnContext.Activity.Locale = activityLocale; - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); // Prompt should default to English if locale is a non-supported value diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs index 412add21..40dad053 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs @@ -40,7 +40,8 @@ public async Task ConfirmPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ConfirmPrompt("ConfirmPrompt", defaultLocale: Culture.English)); @@ -81,7 +82,8 @@ public async Task ConfirmPromptRetry() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ConfirmPrompt("ConfirmPrompt", defaultLocale: Culture.English)); @@ -137,7 +139,8 @@ public async Task ConfirmPromptNoOptions() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new ConfirmPrompt("ConfirmPrompt", defaultLocale: Culture.English)); @@ -187,7 +190,8 @@ public async Task ConfirmPromptChoiceOptionsNumbers() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -249,7 +253,8 @@ public async Task ConfirmPromptChoiceOptionsMultipleAttempts() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -312,7 +317,8 @@ public async Task ConfirmPromptChoiceOptionsNoNumbers() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -370,7 +376,8 @@ public async Task ConfirmPromptDifferentRecognizeLanguage() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -429,7 +436,8 @@ public async Task ShouldUsePromptClassStyleProperty() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -467,7 +475,8 @@ public async Task PromptOptionsStyleShouldOverridePromptClassStyleProperty() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs index b54e3b20..b76a87ff 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs @@ -33,7 +33,8 @@ public async Task BasicDateTimePrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(dateTimePrompt); @@ -73,7 +74,8 @@ public async Task MultipleResolutionsDateTimePrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(dateTimePrompt); @@ -115,7 +117,8 @@ public async Task DateTimePromptWithValidator() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(dateTimePrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 8fa7b935..801a3fc5 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -142,6 +142,7 @@ public async Task RunAsyncShouldSetTelemetryClient() using (var turnContext = new TurnContext(adapter.Object, activity)) { + await conversationState.LoadAsync(turnContext, false); turnContext.TurnState.Add(telemetryClientMock.Object); await DialogExtensions.RunAsync(dialog, turnContext, conversationState, CancellationToken.None); @@ -174,6 +175,8 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc return new TestFlow(adapter, async (turnContext, cancellationToken) => { + await convoState.LoadAsync(turnContext, false, cancellationToken); + if (testCase != FlowTestCase.RootBotOnly) { // Create a skill ClaimsIdentity and put it in TurnState so SkillValidation.IsSkillClaim() returns true. diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs index 850ebaab..a02ad7f0 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs @@ -61,7 +61,8 @@ await Assert.ThrowsAsync(async () => await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPromptMock); @@ -98,7 +99,8 @@ public async Task NumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -135,7 +137,8 @@ public async Task NumberPromptRetry() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -189,7 +192,8 @@ public async Task NumberPromptValidator() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -232,7 +236,8 @@ public async Task FloatNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -272,7 +277,8 @@ public async Task LongNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -312,7 +318,8 @@ public async Task DoubleNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -352,7 +359,8 @@ public async Task CurrencyNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -392,7 +400,8 @@ public async Task AgeNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -432,7 +441,8 @@ public async Task DimensionNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -472,7 +482,8 @@ public async Task TemperatureNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -512,7 +523,8 @@ public async Task CultureThruNumberPromptCtor() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -551,7 +563,8 @@ public async Task CultureThruActivityNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -590,7 +603,8 @@ public async Task NumberPromptDefaultsToEnUsLocale() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -630,7 +644,8 @@ public async Task DecimalNumberPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); @@ -670,7 +685,8 @@ public async Task NoNumberButUnitPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, cancellationToken); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(numberPrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs index 8bf5e037..e015a10e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs @@ -75,8 +75,8 @@ await Assert.ThrowsAsync(async () => .Use(new AutoSaveStateMiddleware(convoState)); var tc = new TurnContext(adapter, new Activity() { Type = ActivityTypes.Message, Conversation = new ConversationAccount() { Id = "123" }, ChannelId = "test" }); - - var dialogState = await convoState.GetPropertyAsync(tc, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(tc, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); // Create new DialogSet. var dialogs = new DialogSet(dialogState); @@ -102,7 +102,8 @@ await Assert.ThrowsAsync(async () => var tc = new TurnContext(adapter, new Activity() { Type = ActivityTypes.Message, Conversation = new ConversationAccount() { Id = "123" }, ChannelId = "test" }); // Create new DialogSet. - var dialogState = await convoState.GetPropertyAsync(tc, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(tc, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(prompt); @@ -122,7 +123,8 @@ public async Task OAuthPromptWithMagicCode() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -240,7 +242,8 @@ public async Task OAuthPromptDoesNotDetectCodeInBeginDialog() // Add a magic code to the adapter preemptively so that we can test if the message that triggers BeginDialogAsync uses magic code detection adapter.AddUserToken(ConnectionName, turnContext.Activity.ChannelId, turnContext.Activity.From.Id, Token, MagicCode); - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -282,7 +285,8 @@ public async Task OAuthPromptWithTokenExchangeInvoke() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -351,7 +355,8 @@ public async Task OAuthPromptWithTokenExchangeFail() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -418,7 +423,8 @@ public async Task OAuthPromptWithTokenExchangeNoBodyFails() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -482,7 +488,8 @@ public async Task OAuthPromptWithTokenExchangeWrongConnectionNameFail() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -549,7 +556,8 @@ public async Task OAuthPromptInNotSupportedChannelShouldAddSignInCard() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { ConnectionName = ConnectionName })); @@ -597,7 +605,8 @@ public async Task OAuthPromptSignInLinkSettingsCases(bool? showSignInLinkValue, BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oAuthPromptSettings)); @@ -681,7 +690,8 @@ public async Task OAuthPromptRecognizeTokenAsync_WithNullTextMessageActivity_Doe BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -725,7 +735,8 @@ public async Task OAuthPromptSasUrlPresentInOAuthCard(TestAdapter testAdapter, b BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", oAuthPromptSettings)); @@ -770,7 +781,8 @@ public async Task OAuthPromptEndOnInvalidMessageSetting() BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in", EndOnInvalidMessage = true })); @@ -849,7 +861,8 @@ private async Task OAuthPrompt(IStorage storage) BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = "Please sign in", ConnectionName = ConnectionName, Title = "Sign in" })); @@ -900,7 +913,8 @@ private async Task PromptTimeoutEndsDialogTest(IActivity oauthPromptActivity) BotCallbackHandler botCallbackHandler = async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), default); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); // Set timeout to zero, so the prompt will end immediately. @@ -982,4 +996,26 @@ public override async Task GetSignInResourceAsync(ITurnContext t } */ } + + public class Location + { + public float? Lat { get; set; } + + public float? Long { get; set; } + } + + + public class Options + { + public string FirstName { get; set; } + + public string LastName { get; set; } + + public int? Age { get; set; } + + public bool? Bool { get; set; } + + public Location Location { get; set; } + } + } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs index fcf5d84c..427eeb25 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs @@ -37,7 +37,8 @@ public async Task PromptValidatorContextEnd() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(new TextPrompt("namePrompt", (promptContext, cancellationToken) => Task.FromResult(true))); @@ -105,7 +106,8 @@ public async Task PromptValidatorContextRetryEnd() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); @@ -171,7 +173,8 @@ public async Task PromptValidatorNumberOfAttempts() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs index 4f807ab3..da47a9a1 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs @@ -28,6 +28,7 @@ public async Task ReplaceDialogNoBranchAsync() await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => { + await conversationState.LoadAsync(turnContext, false, cancellationToken); await dialog.RunAsync(turnContext, conversationState, cancellationToken); }) .Send("hello") @@ -52,6 +53,7 @@ public async Task ReplaceDialogBranchAsync() await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => { + await conversationState.LoadAsync(turnContext, false, cancellationToken); await dialog.RunAsync(turnContext, conversationState, cancellationToken); }) .Send("hello") @@ -79,6 +81,7 @@ public async Task ReplaceDialogTelemetryClientNotNull() await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => { + await conversationState.LoadAsync(turnContext, false, cancellationToken); await dialog.RunAsync(turnContext, conversationState, cancellationToken); Assert.NotNull(dialog.TelemetryClient); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs index 2e892fd2..bc8b2f5b 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs @@ -41,7 +41,8 @@ public async Task TextPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); @@ -87,7 +88,8 @@ public async Task TextPromptWithNaughtyStrings() { await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); @@ -153,7 +155,8 @@ public async Task TextPromptValidator() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); @@ -203,7 +206,8 @@ public async Task TextPromptWithRetryPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); @@ -260,7 +264,8 @@ public async Task TextPromptValidatorWithMessageShouldNotSendRetryPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(textPrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs index d4068eaf..9beaff24 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs @@ -86,7 +86,8 @@ public async Task Waterfall() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(waterfallDialog); @@ -134,7 +135,8 @@ public async Task WaterfallStepParentIsWaterfallParent() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(waterfallParent); @@ -182,7 +184,8 @@ public async Task WaterfallWithCallback() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(waterfallDialog); @@ -220,7 +223,8 @@ public async Task WaterfallWithClass() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(waterfallDialog); @@ -250,7 +254,8 @@ public async Task WaterfallPrompt() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(Create_Waterfall2()); @@ -294,7 +299,8 @@ public async Task WaterfallNested() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(Create_Waterfall3()); @@ -351,7 +357,8 @@ public async Task WaterfallDateTimePromptFirstInvalidThenValidInput() await new TestFlow(adapter, async (turnContext, cancellationToken) => { - var dialogState = await convoState.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken); + await convoState.LoadAsync(turnContext, false, default); + var dialogState = convoState.GetValue("DialogState", () => new DialogState()); var dialogs = new DialogSet(dialogState); dialogs.Add(dateTimePrompt); diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs index 7b7e2939..de8f05e3 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs @@ -37,6 +37,9 @@ public DialogBot(ConversationState conversationState, UserState userState, T dia public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) { + await ConversationState.LoadAsync(turnContext, false, cancellationToken); + await UserState.LoadAsync(turnContext, false, cancellationToken); + await base.OnTurnAsync(turnContext, cancellationToken); // Save any state changes that might have occurred during the turn. diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs index 91fa68af..8fd02fec 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs @@ -14,7 +14,7 @@ public static class DialogExtensions { public static async Task Run(this Dialog dialog, ITurnContext turnContext, BotState state, CancellationToken cancellationToken) { - var dialogState = await state.GetPropertyAsync(turnContext, "DialogState", () => new DialogState(), cancellationToken).ConfigureAwait(false); + var dialogState = state.GetValue("DialogState", () => new DialogState()); var dialogSet = new DialogSet(dialogState); dialogSet.Add(dialog); diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs index 19725b6c..4f3227a3 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs @@ -120,7 +120,7 @@ private async Task SummaryActionAsync(WaterfallStepContext ste if ((bool)stepContext.Result) { // Get the current profile object from user state. - var userProfile = await _userState.GetPropertyAsync(stepContext.Context, "UserProfile", () => new UserProfile(), cancellationToken); + var userProfile = _userState.GetValue("UserProfile", () => new UserProfile()); userProfile.Transport = (string)stepContext.Values["transport"]; userProfile.Name = (string)stepContext.Values["name"]; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/AutoSaveStateMiddlewareTests.cs b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs similarity index 80% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/AutoSaveStateMiddlewareTests.cs rename to src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs index ae1c1fdd..824de710 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/AutoSaveStateMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs @@ -4,12 +4,11 @@ using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.State; using Microsoft.Agents.Storage; using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.BotBuilder.Tests +namespace Microsoft.Agents.State.Tests { public class AutoSaveStateMiddlewareTests { @@ -21,15 +20,15 @@ public async Task AutoSaveStateMiddleware_DualReadWrite() var convState = new ConversationState(storage); var adapter = new TestAdapter(TestAdapter.CreateConversation("AutoSaveStateMiddleware_DualReadWrite")) - .Use(new AutoSaveStateMiddleware(userState, convState)); + .Use(new AutoSaveStateMiddleware(true, userState, convState)); const int USER_INITITAL_COUNT = 100; const int CONVERSATION_INITIAL_COUNT = 10; BotCallbackHandler botLogic = async (context, cancellationToken) => { // get userCount and convCount from hSet - var userCount = await userState.GetPropertyAsync(context, "userCount", () => USER_INITITAL_COUNT, cancellationToken).ConfigureAwait(false); - var convCount = await convState.GetPropertyAsync(context, "convCount", () => CONVERSATION_INITIAL_COUNT, cancellationToken).ConfigureAwait(false); + var userCount = userState.GetValue("userCount", () => USER_INITITAL_COUNT); + var convCount = convState.GetValue("convCount", () => CONVERSATION_INITIAL_COUNT); // System.Diagnostics.Debug.WriteLine($"{context.Activity.Id} UserCount({context.Activity.From.Id}):{userCount} convCount({context.Activity.Conversation.Id}):{convCount}"); if (context.Activity.Type == ActivityTypes.Message) @@ -46,11 +45,11 @@ public async Task AutoSaveStateMiddleware_DualReadWrite() // increment userCount and set property using accessor. To be saved later by AutoSaveStateMiddleware userCount++; - await userState.SetPropertyAsync(context, "userCount", userCount, cancellationToken); + userState.SetValue("userCount", userCount); // increment convCount and set property using accessor. To be saved later by AutoSaveStateMiddleware convCount++; - await convState.SetPropertyAsync(context, "convCount", convCount, cancellationToken); + convState.SetValue("convCount", convCount); }; await new TestFlow(adapter, botLogic) @@ -63,6 +62,11 @@ public async Task AutoSaveStateMiddleware_DualReadWrite() .AssertReply((CONVERSATION_INITIAL_COUNT + 3).ToString()) .StartTestAsync(); + + // Reuse storage with a different conversation + userState = new UserState(storage); + convState = new ConversationState(storage); + // new adapter on new conversation adapter = new TestAdapter(new ConversationReference { @@ -72,7 +76,7 @@ public async Task AutoSaveStateMiddleware_DualReadWrite() Bot = new ChannelAccount("bot", "Bot"), Conversation = new ConversationAccount(false, "convo2", "Conversation2"), }) - .Use(new AutoSaveStateMiddleware(userState, convState)); + .Use(new AutoSaveStateMiddleware(true, userState, convState)); await new TestFlow(adapter, botLogic) .Send("get userCount") @@ -90,9 +94,7 @@ public async Task AutoSaveStateMiddleware_Chain() var convState = new ConversationState(storage); var userState = new UserState(storage); - var bss = new AutoSaveStateMiddleware() - .Add(userState) - .Add(convState); + var bss = new AutoSaveStateMiddleware(true, convState, userState); var adapter = new TestAdapter(TestAdapter.CreateConversation("AutoSaveStateMiddleware_Chain")) .Use(bss); @@ -101,8 +103,8 @@ public async Task AutoSaveStateMiddleware_Chain() BotCallbackHandler botLogic = async (context, cancellationToken) => { // get userCount and convCount from botStateSet - var userCount = await userState.GetPropertyAsync(context, "userCount", () => USER_INITITAL_COUNT, cancellationToken).ConfigureAwait(false); - var convCount = await convState.GetPropertyAsync(context, "convCount", () => CONVERSATION_INITIAL_COUNT, cancellationToken).ConfigureAwait(false); + var userCount = userState.GetValue("userCount", () => USER_INITITAL_COUNT); + var convCount = convState.GetValue("convCount", () => CONVERSATION_INITIAL_COUNT); if (context.Activity.Type == ActivityTypes.Message) { @@ -118,11 +120,11 @@ public async Task AutoSaveStateMiddleware_Chain() // increment userCount and set property using accessor. To be saved later by AutoSaveStateMiddleware userCount++; - await userState.SetPropertyAsync(context, "userCount", userCount, cancellationToken); + userState.SetValue("userCount", userCount); // increment convCount and set property using accessor. To be saved later by AutoSaveStateMiddleware convCount++; - await convState.SetPropertyAsync(context, "convCount", convCount, cancellationToken); + convState.SetValue("convCount", convCount); }; await new TestFlow(adapter, botLogic) @@ -136,9 +138,10 @@ public async Task AutoSaveStateMiddleware_Chain() .StartTestAsync(); // new adapter on new conversation - var bss2 = new AutoSaveStateMiddleware() - .Add(userState) - .Add(convState); + // Reuse storage with a different conversation + userState = new UserState(storage); + convState = new ConversationState(storage); + bss = new AutoSaveStateMiddleware(true, convState, userState); adapter = new TestAdapter(new ConversationReference { @@ -148,7 +151,7 @@ public async Task AutoSaveStateMiddleware_Chain() Bot = new ChannelAccount("bot", "Bot"), Conversation = new ConversationAccount(false, "convo2", "Conversation2"), }) - .Use(bss2); + .Use(bss); await new TestFlow(adapter, botLogic) .Send("get userCount") diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs index 8c04cbe9..ab401d3c 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using Microsoft.Agents.Storage; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -10,7 +12,7 @@ namespace Microsoft.Agents.State.Tests public class BotStateSetTests { [Fact] - public void BotStateSet_Properties() + public void TurnState_Properties() { var storage = new MemoryStorage(); @@ -20,16 +22,14 @@ public void BotStateSet_Properties() // setup convState var convState = new ConversationState(storage); - var stateSet = new BotStateSet(userState, convState); + var turnState = new BotStateSet(userState, convState); - Assert.Equal(2, stateSet.BotStates.Count); - - Assert.IsType(stateSet.BotStates[nameof(UserState)]); - Assert.IsType(stateSet.BotStates[nameof(ConversationState)]); + Assert.IsType(turnState.GetScope(userState.ContextServiceKey)); + Assert.IsType(turnState.GetScope(convState.ContextServiceKey)); } [Fact] - public async Task BotStateSet_LoadAsync() + public async Task TurnState_LoadAsync() { var storage = new MemoryStorage(); @@ -41,19 +41,21 @@ public async Task BotStateSet_LoadAsync() // setup convState var convState = new ConversationState(storage); - var stateSet = new BotStateSet(userState, convState); - - Assert.Equal(2, stateSet.BotStates.Count); + var turnState = new BotStateSet(userState, convState); + await turnState.LoadAllAsync(turnContext, false); - var userCount = await userState.GetPropertyAsync(turnContext, "userCount", () => 0, default); + var userCount = turnState.GetValue(turnContext, "user.userCount", () => 0); Assert.Equal(0, userCount); - var convCount = await convState.GetPropertyAsync(turnContext, "convCount", () => 0, default); + var convCount = turnState.GetValue(turnContext, "conversation.convCount", () => 0); Assert.Equal(0, convCount); - await userState.SetPropertyAsync(turnContext, "userCount", 10, default); - await convState.SetPropertyAsync(turnContext, "convCount", 20, default); + turnState.SetValue(turnContext, "user.userCount", 10); + turnState.SetValue(turnContext, "conversation.convCount", 20); - await stateSet.SaveAllChangesAsync(turnContext); + Assert.Equal(10, turnState.GetValue(turnContext, "user.userCount", () => 0)); + Assert.Equal(20, turnState.GetValue(turnContext, "conversation.convCount", () => 0)); + + await turnState.SaveAllChangesAsync(turnContext, false); } { @@ -63,19 +65,119 @@ public async Task BotStateSet_LoadAsync() // setup convState var convState = new ConversationState(storage); - var stateSet = new BotStateSet(userState, convState); + var turnState = new BotStateSet(userState, convState); - await stateSet.LoadAllAsync(turnContext); + await turnState.LoadAllAsync(turnContext); - var userCount = await userState.GetPropertyAsync(turnContext, "userCount", () => 0, default); + var userCount = turnState.GetValue(turnContext, "user.userCount", () => 0); Assert.Equal(10, userCount); - var convCount = await convState.GetPropertyAsync(turnContext, "convCount", () => 0, default); + var convCount = turnState.GetValue(turnContext, "conversation.convCount", () => 0); Assert.Equal(20, convCount); } } [Fact] - public async Task BotStateSet_ReturnsDefaultForNullValueType() + public async Task TurnState_DottedProperties() + { + var test = new + { + test = "test", + + options = new + { + Age = 15, + FirstName = "joe", + LastName = "blow", + Bool = false, + Nicknames = new List( [ "Tom", "Rex" ] ) + }, + + bar = new + { + numIndex = 2, + strIndex = "FirstName", + objIndex = "options", + options = new Options() + { + Age = 1, + FirstName = "joe", + LastName = "blow", + Bool = false, + }, + numbers = new int[] { 1, 2, 3, 4, 5 } + }, + }; + + var storage = new MemoryStorage(); + + var turnContext = TestUtilities.CreateEmptyContext(); + { + var userState = new UserState(storage); + var convState = new ConversationState(storage); + var turnState = new BotStateSet(userState, convState); + + await turnState.LoadAllAsync(turnContext, false); + + // Add a couple array elements + turnState.SetValue(turnContext, "conversation.x.a[1]", "yabba"); + turnState.SetValue(turnContext, "conversation.x.a[0]", "dabba"); + + Assert.Equal("dabba", turnState.GetValue(turnContext, "conversation.x.a[0]", () => string.Empty)); + Assert.Equal("yabba", turnState.GetValue(turnContext, "conversation.x.a[1]", () => string.Empty)); + + // Verify array + var array = turnState.GetValue(turnContext, "conversation.x.a", () => Array.Empty()); + Assert.IsAssignableFrom(array); + Assert.Equal(2, array.Length); + Assert.Equal("dabba", array[0]); + Assert.Equal("yabba", array[1]); + + // Anonymous type access + turnState.SetValue(turnContext, "user.test", test); + + Assert.Equal("FirstName", turnState.GetValue(turnContext, "user.test.bar.strIndex", () => string.Empty)); + Assert.Equal("Rex", turnState.GetValue(turnContext, "user.test.options.Nicknames[1]", () => string.Empty)); + Assert.Equal(2, turnState.GetValue(turnContext, "user.test.bar.numbers[1]", () => -1)); + + // Anonymous types are read only + Assert.Throws(() => turnState.SetValue(turnContext, "user.test.bar.strIndex", "NewFirstName")); + + // Don't support growing native arrays yet + Assert.Throws(() => turnState.SetValue(turnContext, "user.test.bar.numbers[10]", 10)); + + // But it can grow lists + turnState.SetValue(turnContext, "user.test.options.Nicknames[5]", "John"); + Assert.Equal("John", turnState.GetValue(turnContext, "user.test.options.Nicknames[5]", () => string.Empty)); + + // Can set poco + var poco = new TestPocoState() + { + Value = "firstValue" + }; + turnState.SetValue(turnContext, "user.poco", poco); + Assert.Equal("firstValue", turnState.GetValue(turnContext, "user.poco.Value", () => string.Empty)); + + // Get poco object + var pocoOut = turnState.GetValue(turnContext, "user.poco", () => new TestPocoState()); + Assert.Equal("firstValue", pocoOut.Value); + + // Can set poco field + turnState.SetValue(turnContext, "user.poco.Value", "secondValue"); + Assert.Equal("secondValue", turnState.GetValue(turnContext, "user.poco.Value", () => string.Empty)); + + // List + turnState.SetValue(turnContext, "conversation.chatHistory", new List()); + var chatHistory = turnState.GetValue(turnContext, "conversation.chatHistory", () => new List()); + chatHistory.Add("Hello"); + chatHistory.Add("Howdy"); + + Assert.Equal("Hello", turnState.GetValue(turnContext, "conversation.chatHistory[0]", () => string.Empty)); + Assert.Equal("Howdy", turnState.GetValue(turnContext, "conversation.chatHistory[1]", () => string.Empty)); + } + } + + [Fact] + public async Task TurnState_ReturnsDefaultForNullValueType() { var storage = new MemoryStorage(); @@ -87,27 +189,26 @@ public async Task BotStateSet_ReturnsDefaultForNullValueType() // setup convState var convState = new ConversationState(storage); - var stateSet = new BotStateSet(userState, convState); - - Assert.Equal(2, stateSet.BotStates.Count); + var turnState = new BotStateSet(userState, convState); + await turnState.LoadAllAsync(turnContext, false); - var userObject = await userState.GetPropertyAsync(turnContext, "userStateObject", () => null, default); + var userObject = turnState.GetValue(turnContext, "user.userStateObject", () => null); Assert.Null(userObject); // Ensure we also get null on second attempt - userObject = await userState.GetPropertyAsync(turnContext, "userStateObject", () => null, default); + userObject = turnState.GetValue(turnContext, "user.userStateObject", () => null); Assert.Null(userObject); - var convObject = await convState.GetPropertyAsync(turnContext, "convStateObject", () => null, default); + var convObject = turnState.GetValue(turnContext, "conversation.convStateObject", () => null); Assert.Null(convObject); // Ensure we also get null on second attempt - convObject = await convState.GetPropertyAsync(turnContext, "convStateObject", () => null, default); + convObject = turnState.GetValue(turnContext, "conversation.convStateObject", () => null); Assert.Null(convObject); } [Fact] - public async Task BotStateSet_SaveAsync() + public async Task TurnState_SaveAsync() { var storage = new MemoryStorage(); @@ -117,26 +218,25 @@ public async Task BotStateSet_SaveAsync() // setup convState var convState = new ConversationState(storage); - var stateSet = new BotStateSet(userState, convState); + var turnState = new BotStateSet(userState, convState); - Assert.Equal(2, stateSet.BotStates.Count); var context = TestUtilities.CreateEmptyContext(); - await stateSet.LoadAllAsync(context); + await turnState.LoadAllAsync(context); - var userCount = await userState.GetPropertyAsync(context, "userCount", () => 0, default); + var userCount = userState.GetValue("userCount", () => 0); Assert.Equal(0, userCount); - var convCount = await convState.GetPropertyAsync(context, "convCount", () => 0, default); + var convCount = convState.GetValue("convCount", () => 0); Assert.Equal(0, convCount); - await userState.SetPropertyAsync(context, "userCount", 10, default); - await convState.SetPropertyAsync(context, "convCount", 20, default); + userState.SetValue("userCount", 10); + convState.SetValue("convCount", 20); - await stateSet.SaveAllChangesAsync(context); + await turnState.SaveAllChangesAsync(context); - userCount = await userState.GetPropertyAsync(context, "userCount", () => 0, default); + userCount = userState.GetValue("userCount", () => 0); Assert.Equal(10, userCount); - convCount = await convState.GetPropertyAsync(context, "convCount", () => 0, default); + convCount = convState.GetValue("convCount", () => 0); Assert.Equal(20, convCount); } diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs index ee949025..5b724631 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs @@ -6,10 +6,12 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; +using Microsoft.VisualBasic; using Moq; using Xunit; @@ -25,8 +27,10 @@ public async Task State_EmptyName() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context); + // Act - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, string.Empty, () => string.Empty, default)); + Assert.Throws(() => userState.GetValue(string.Empty, () => string.Empty)); } [Fact] @@ -37,8 +41,10 @@ public async Task State_NullName() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context); + // Act - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, null, () => string.Empty, default)); + Assert.Throws(() => userState.GetValue(null, () => string.Empty)); } [Fact] @@ -75,24 +81,27 @@ public async Task MakeSureStorageNotCalledNoChangesAsync() // Arrange var userState = new UserState(mock.Object); + var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act Assert.Equal(0, storeCount); await userState.SaveChangesAsync(context); - await userState.SetPropertyAsync(context, "propertyA", "hello", default); + userState.SetValue("propertyA", "hello"); Assert.Equal(1, readCount); // Initial save bumps count Assert.Equal(0, storeCount); // Initial save bumps count - await userState.SetPropertyAsync(context, "propertyA", "there", default); + userState.SetValue("propertyA", "there"); Assert.Equal(0, storeCount); // Set on property should not bump await userState.SaveChangesAsync(context); Assert.Equal(1, storeCount); // Explicit save should bump - var valueA = await userState.GetPropertyAsync(context, "propertyA", () => string.Empty, default); + var valueA = userState.GetValue("propertyA", () => string.Empty); Assert.Equal("there", valueA); Assert.Equal(1, storeCount); // Gets should not bump await userState.SaveChangesAsync(context); Assert.Equal(1, storeCount); - await userState.DeletePropertyAsync(context, "propertyA", default); // Delete alone no bump + userState.DeleteValue("propertyA"); // Delete alone no bump Assert.Equal(1, storeCount); await userState.SaveChangesAsync(context); // Save when dirty should bump Assert.Equal(2, storeCount); @@ -110,8 +119,10 @@ public async Task State_SetNoLoad() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - await userState.SetPropertyAsync(context, "propertyA", "hello", default); + userState.SetValue("propertyA", "hello"); } [Fact] @@ -135,8 +146,10 @@ public async Task State_GetNoLoadWithDefault() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - var valueA = await userState.GetPropertyAsync(context, "propertyA", () => "Default!", default); + var valueA = userState.GetValue("propertyA", () => "Default!"); Assert.Equal("Default!", valueA); } @@ -148,8 +161,10 @@ public async Task State_GetNoLoadNoDefault() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - var valueA = await userState.GetPropertyAsync(context, "propertyA", null, default); + var valueA = userState.GetValue("propertyA"); // Assert Assert.Null(valueA); @@ -163,8 +178,10 @@ public async Task State_POCO_NoDefault() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - var value = await userState.GetPropertyAsync(context, "test", null, default); + var value = userState.GetValue("test"); // Assert Assert.Null(value); @@ -178,8 +195,10 @@ public async Task State_bool_NoDefault() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - var value = await userState.GetPropertyAsync(context, "test", null, default); + var value = userState.GetValue("test", null); // Assert Assert.False(value); @@ -193,8 +212,10 @@ public async Task State_int_NoDefault() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act - var value = await userState.GetPropertyAsync(context, "test", null, default); + var value = userState.GetValue("test", null); // Assert Assert.Equal(0, value); @@ -210,11 +231,11 @@ public async Task State_SetAfterSave() // Act await userState.LoadAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello", default); - await userState.SetPropertyAsync(context, "property-b", "world", default); + userState.SetValue("property-a", "hello"); + userState.SetValue("property-b", "world"); await userState.SaveChangesAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello2", default); + userState.SetValue("property-a", "hello2"); } [Fact] @@ -227,13 +248,13 @@ public async Task State_MultipleSave() // Act await userState.LoadAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello", default); - await userState.SetPropertyAsync(context, "property-b", "world", default); + userState.SetValue("property-a", "hello"); + userState.SetValue("property-b", "world"); await userState.SaveChangesAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello2", default); + userState.SetValue("property-a", "hello2"); await userState.SaveChangesAsync(context); - var valueA = await userState.GetPropertyAsync(context, "property-a", null, default); + var valueA = userState.GetValue("property-a"); Assert.Equal("hello2", valueA); } @@ -247,8 +268,8 @@ public async Task LoadSetSave() // Act await userState.LoadAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello", default); - await userState.SetPropertyAsync(context, "property-b", "world", default); + userState.SetValue("property-a", "hello"); + userState.SetValue("property-b", "world"); await userState.SaveChangesAsync(context); // Assert @@ -268,9 +289,9 @@ public async Task LoadSetSaveTwice() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); await userState.LoadAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello", default); - await userState.SetPropertyAsync(context, "property-b", "world", default); - await userState.SetPropertyAsync(context, "property-c", "test", default); + userState.SetValue("property-a", "hello"); + userState.SetValue("property-b", "world"); + userState.SetValue("property-c", "test"); await userState.SaveChangesAsync(context); // Assert @@ -282,8 +303,8 @@ public async Task LoadSetSaveTwice() var userState2 = new UserState(new MemoryStorage(dictionary: dictionary)); await userState2.LoadAsync(context); - await userState2.SetPropertyAsync(context, "property-a", "hello-2", default); - await userState2.SetPropertyAsync(context, "property-b", "world-2", default); + userState2.SetValue("property-a", "hello-2"); + userState2.SetValue("property-b", "world-2"); await userState2.SaveChangesAsync(context); // Assert 2 @@ -304,8 +325,8 @@ public async Task LoadSaveDelete() var userState = new UserState(new MemoryStorage(dictionary: dictionary)); await userState.LoadAsync(context); - await userState.SetPropertyAsync(context, "property-a", "hello", default); - await userState.SetPropertyAsync(context, "property-b", "world", default); + userState.SetValue("property-a", "hello"); + userState.SetValue("property-b", "world"); await userState.SaveChangesAsync(context); // Assert @@ -317,8 +338,8 @@ public async Task LoadSaveDelete() var userState2 = new UserState(new MemoryStorage(dictionary: dictionary)); await userState2.LoadAsync(context); - await userState2.SetPropertyAsync(context, "property-a", "hello-2", default); - await userState2.DeletePropertyAsync(context, "property-b", default); + userState2.SetValue("property-a", "hello-2"); + userState2.DeleteValue("property-b"); await userState2.SaveChangesAsync(context); // Assert 2 @@ -353,7 +374,8 @@ public async Task State_RememberIStoreItemUserState() adapter, async (context, cancellationToken) => { - var state = await userState.GetPropertyAsync(context, "test", () => new TestState(), default); + await userState.LoadAsync(context, false); + var state = userState.GetValue("test", () => new TestState()); Assert.NotNull(state); switch (context.Activity.Text) { @@ -381,7 +403,8 @@ public async Task State_RememberPocoUserState() adapter, async (context, cancellationToken) => { - var testPocoState = await userState.GetPropertyAsync(context, "testPoco", () => new TestPocoState(), cancellationToken); + await userState.LoadAsync(context, false, cancellationToken); + var testPocoState = userState.GetValue("testPoco", () => new TestPocoState()); Assert.NotNull(userState); switch (context.Activity.Text) { @@ -411,7 +434,8 @@ public async Task State_RememberIStoreItemConversationState() adapter, async (context, cancellationToken) => { - var conversationState = await userState.GetPropertyAsync(context, "test", () => new TestState(), cancellationToken); + await userState.LoadAsync(context, false, cancellationToken); + var conversationState = userState.GetValue("test", () => new TestState()); Assert.NotNull(conversationState); switch (context.Activity.Text) { @@ -440,7 +464,8 @@ public async Task State_RememberPocoConversationState() adapter, async (context, cancellationToken) => { - var conversationState = await userState.GetPropertyAsync(context, "testPoco", () => new TestPocoState(), cancellationToken); + await userState.LoadAsync(context, false, cancellationToken); + var conversationState = userState.GetValue("testPoco", () => new TestPocoState()); Assert.NotNull(conversationState); switch (context.Activity.Text) { @@ -469,7 +494,8 @@ public async Task State_RememberPocoPrivateConversationState() adapter, async (context, cancellationToken) => { - var conversationState = await privateConversationState.GetPropertyAsync(context, "testPoco", () => new TestPocoState(), cancellationToken); + await privateConversationState.LoadAsync(context, false); + var conversationState = privateConversationState.GetValue("testPoco", () => new TestPocoState()); Assert.NotNull(conversationState); switch (context.Activity.Text) { @@ -498,7 +524,8 @@ public async Task State_CustomStateManagerTest() await new TestFlow(adapter, async (context, cancellationToken) => { - var test = await customState.GetPropertyAsync(context, "test", () => new TestPocoState(), cancellationToken); + await customState.LoadAsync(context, false, cancellationToken); + var test = customState.GetValue("test", () => new TestPocoState()); switch (context.Activity.Text) { case "set value": @@ -526,7 +553,8 @@ public async Task State_RoundTripTypedObject() adapter, async (context, cancellationToken) => { - var conversation = await convoState.GetPropertyAsync(context, "typed", () => new TypedObject(), cancellationToken); + await convoState.LoadAsync(context, false, cancellationToken); + var conversation = convoState.GetValue("typed", () => new TypedObject()); Assert.NotNull(conversation); switch (context.Activity.Text) { @@ -558,7 +586,7 @@ public async Task State_UseBotStateDirectly() // read initial state object await botStateManager.LoadAsync(context); - var customState = await botStateManager.GetPropertyAsync(context, "test", () => new CustomState(), cancellationToken); + var customState = botStateManager.GetValue("test", () => new CustomState()); // this should be a 'new CustomState' as nothing is currently stored in storage Assert.NotNull(customState); @@ -574,7 +602,7 @@ public async Task State_UseBotStateDirectly() // read into context again await botStateManager.LoadAsync(context, force: true); - customState = await botStateManager.GetPropertyAsync(context, "test", () => new CustomState(), cancellationToken); + customState = botStateManager.GetValue("test", () => new CustomState()); // check object read from value has the correct value for CustomString Assert.Equal("test", customState.CustomString); @@ -587,31 +615,35 @@ public async Task State_UseBotStateDirectly() public async Task UserState_BadFromThrows() { var dictionary = new Dictionary(); - var userState = new UserState(new MemoryStorage(dictionary: dictionary)); + var botState = new UserState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); context.Activity.From = null; - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, "test", null, default)); + await Assert.ThrowsAsync(() => botState.LoadAsync(context, false)); } [Fact] public async Task ConversationState_BadConverationThrows() { var dictionary = new Dictionary(); - var userState = new ConversationState(new MemoryStorage(dictionary: dictionary)); + var botState = new ConversationState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + context.Activity.Conversation = null; - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, "test", null, default)); + + await Assert.ThrowsAsync(() => botState.LoadAsync(context, false)); } [Fact] public async Task PrivateConversationState_BadActivityFromThrows() { var dictionary = new Dictionary(); - var userState = new PrivateConversationState(new MemoryStorage(dictionary: dictionary)); + var botState = new PrivateConversationState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + context.Activity.Conversation = null; context.Activity.From = null; - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, "test", null, default)); + + await Assert.ThrowsAsync(() => botState.LoadAsync(context, false)); } [Fact] @@ -620,8 +652,10 @@ public async Task PrivateConversationState_BadActivityConversationThrows() var dictionary = new Dictionary(); var userState = new PrivateConversationState(new MemoryStorage(dictionary: dictionary)); var context = TestUtilities.CreateEmptyContext(); + context.Activity.Conversation = null; - await Assert.ThrowsAsync(() => userState.GetPropertyAsync(context, "test", null, default)); + + await Assert.ThrowsAsync(() => userState.LoadAsync(context, false)); } [Fact] @@ -634,26 +668,32 @@ public async Task ClearAndSave() // Turn 0 var botState1 = new ConversationState(storage); - (await botState1 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState(), default)).Value = "test-value"; + await botState1.LoadAsync(turnContext, false); + var value1 = botState1 + .GetValue("test-name", () => new TestPocoState()); + value1.Value = "test-value"; + //await botState1.SetValue(turnContext, "test-name", value1, default); await botState1.SaveChangesAsync(turnContext); // Turn 1 var botState2 = new ConversationState(storage); - var value1 = (await botState2 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState { Value = "default-value" }, default)).Value; + await botState2.LoadAsync(turnContext, false); + value1 = botState2 + .GetValue("test-name", () => new TestPocoState { Value = "default-value" }); - Assert.Equal("test-value", value1); + Assert.Equal("test-value", value1.Value); // Turn 2 var botState3 = new ConversationState(storage); - await botState3.ClearStateAsync(turnContext); + await botState3.LoadAsync(turnContext, false); + botState3.ClearState(); await botState3.SaveChangesAsync(turnContext); // Turn 3 var botState4 = new ConversationState(storage); - var value2 = (await botState4 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState { Value = "default-value" }, default)).Value; + await botState4.LoadAsync(turnContext, false); + var value2 = (botState4 + .GetValue("test-name", () => new TestPocoState { Value = "default-value" })).Value; Assert.Equal("default-value", value2); } @@ -668,25 +708,30 @@ public async Task BotStateDelete() // Turn 0 var botState1 = new ConversationState(storage); - (await botState1 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState(), default)).Value = "test-value"; + await botState1.LoadAsync(turnContext, false); + + (botState1 + .GetValue("test-name", () => new TestPocoState())).Value = "test-value"; await botState1.SaveChangesAsync(turnContext); // Turn 1 var botState2 = new ConversationState(storage); - var value1 = (await botState2 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState { Value = "default-value" }, default)).Value; + await botState2.LoadAsync(turnContext, false); + var value1 = (botState2 + .GetValue("test-name", () => new TestPocoState { Value = "default-value" })).Value; Assert.Equal("test-value", value1); // Turn 2 var botState3 = new ConversationState(storage); + await botState3.LoadAsync(turnContext, false); await botState3.DeleteStateAsync(turnContext); // Turn 3 var botState4 = new ConversationState(storage); - var value2 = (await botState4 - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState { Value = "default-value" }, default)).Value; + await botState4.LoadAsync(turnContext, false); + var value2 = (botState4 + .GetValue("test-name", () => new TestPocoState { Value = "default-value" })).Value; Assert.Equal("default-value", value2); } @@ -703,10 +748,11 @@ public async Task BotStateGet() // because TestBotState has a context service key // that is different from the name of its type var botState = new TestBotState(storage); - (await botState - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState(), default)).Value = "test-value"; + await botState.LoadAsync(turnContext, false); + (botState + .GetValue("test-name", () => new TestPocoState())).Value = "test-value"; - var json = JsonObject.Create(botState.Get(turnContext)); + var json = JsonObject.Create(botState.Get()); Assert.Equal("test-value", json["test-name"]["value"].ToString()); } @@ -719,15 +765,16 @@ public async Task BotStateGetCachedState() var storage = new MemoryStorage(dictionary: new Dictionary()); var botState = new TestBotState(storage); + await botState.LoadAsync(turnContext, false); - (await botState - .GetPropertyAsync(turnContext, "test-name", () => new TestPocoState(), default)).Value = "test-value"; + (botState + .GetValue("test-name", () => new TestPocoState())).Value = "test-value"; - var cache = botState.GetCachedState(turnContext); + var cache = botState.GetCachedState(); Assert.NotNull(cache); - Assert.Same(cache, botState.GetCachedState(turnContext)); + Assert.Same(cache, botState.GetCachedState()); } [Fact] @@ -759,9 +806,11 @@ public async Task State_ForceCallsSaveWithoutCachedBotStateChanges() var userState = new UserState(mock.Object); var context = TestUtilities.CreateEmptyContext(); + await userState.LoadAsync(context, false); + // Act // Set initial value and save - await userState.SetPropertyAsync(context, "propertyA", "test", default); + userState.SetValue("propertyA", "test"); await userState.SaveChangesAsync(context); // Assert diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ObjectPathTests.cs b/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ObjectPathTests.cs rename to src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs index d9e62f85..9bad3cfb 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ObjectPathTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs @@ -9,7 +9,7 @@ using Xunit; using Xunit.Sdk; -namespace Microsoft.Agents.BotBuilder.Dialogs.Tests +namespace Microsoft.Agents.State.Tests { public class ObjectPathTests { From 73f0866439ff4ee024d8af29d8645f3967b77952 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 23 Jan 2025 09:18:19 -0600 Subject: [PATCH 02/60] BotStateSet: Teams AI TurnState compat --- .../AutoSaveStateMiddleware.cs | 4 +- .../Core/Microsoft.Agents.State/BotState.cs | 47 +++--- .../Microsoft.Agents.State/BotStateSet.cs | 79 ++++++++-- .../ConversationState.cs | 4 +- .../Core/Microsoft.Agents.State/IBotState.cs | 24 +++ .../PrivateConversationState.cs | 4 +- .../Core/Microsoft.Agents.State/TempState.cs | 94 +++++++++++ .../Core/Microsoft.Agents.State/UserState.cs | 4 +- src/samples/AuthenticationBot/Program.cs | 2 +- .../BotStateSetTests.cs | 148 +++++++++--------- .../BotStateTests.cs | 37 ++++- 11 files changed, 320 insertions(+), 127 deletions(-) create mode 100644 src/libraries/Core/Microsoft.Agents.State/IBotState.cs create mode 100644 src/libraries/Core/Microsoft.Agents.State/TempState.cs diff --git a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs b/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs index 0123dfbf..faab5916 100644 --- a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs +++ b/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs @@ -84,13 +84,13 @@ public AutoSaveStateMiddleware Add(BotState botState) // before turn if (_autoLoad) { - await BotStateSet.LoadAllAsync(turnContext, true, cancellationToken).ConfigureAwait(false); + await BotStateSet.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); } await next(cancellationToken).ConfigureAwait(false); // after turn - await BotStateSet.SaveAllChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + await BotStateSet.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs index c73e5721..3504332a 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotState.cs @@ -20,39 +20,38 @@ namespace Microsoft.Agents.State /// /// Each state management object defines a scope for a storage layer. /// - /// State properties are created within a state management scope, and the Bot Framework + /// State properties are created within a state management scope, and the Agents SDK /// defines these scopes: /// , , and . /// /// You can define additional scopes for your bot. /// /// - public abstract class BotState : IPropertyManager + public abstract class BotState : IPropertyManager, IBotState { - private readonly string _contextServiceKey; private readonly IStorage _storage; - private CachedBotState _cachedBotState; + private CachedBotState _cachedBotState; /// /// Initializes a new instance of the class. /// /// The storage layer this state management object will use to store /// and retrieve state. - /// The key for the state cache for this . + /// The key for the state cache for this . /// This constructor creates a state management object and associated scope. /// The object uses to persist state property values. - /// The object uses the to cache state within the context for each turn. + /// The object uses the to cache state within the context for each turn. /// - /// or + /// or /// is null. /// - public BotState(IStorage storage, string contextServiceKey) + public BotState(IStorage storage, string stateName) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - _contextServiceKey = contextServiceKey ?? throw new ArgumentNullException(nameof(contextServiceKey)); + Name = stateName ?? throw new ArgumentNullException(nameof(stateName)); } - public string ContextServiceKey { get { return _contextServiceKey; } } + public string Name { get; private set; } /// /// Creates a named state property within the scope of a and returns @@ -62,7 +61,7 @@ public BotState(IStorage storage, string contextServiceKey) /// The name of the property. /// An accessor for the property. /// is null. - [Obsolete("Use BotState.GetPropertyAsync")] + [Obsolete("Use BotState.GetValue and BotState.SetValue")] public IStatePropertyAccessor CreateProperty(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); @@ -78,7 +77,7 @@ public void DeleteValue(string name) { if (!IsLoaded()) { - throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + throw new InvalidOperationException($"{Name} is not loaded"); } DeletePropertyValue(name); @@ -95,7 +94,7 @@ public T GetValue(string name, Func defaultValueFactory = null) { if (!IsLoaded()) { - throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + throw new InvalidOperationException($"{Name} is not loaded"); } T result = default; @@ -136,7 +135,7 @@ public void SetValue(string name, T value) { if (!IsLoaded()) { - throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + throw new InvalidOperationException($"{Name} is not loaded"); } SetPropertyValue(name, value); @@ -181,7 +180,6 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false } else if (val is JsonObject || val is JsonElement) { - // If types are not used by storage serialization, try deserializing to object _cachedBotState = new CachedBotState(storageKey, ProtocolJsonSerializer.ToObject>(val)); } else if (val == null) @@ -199,12 +197,13 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false private bool ShouldLoad(string storageKey, bool force) { - if (_cachedBotState != null && _cachedBotState.Key != storageKey) + var cachedState = GetCachedState(); + if (cachedState != null && cachedState.Key != storageKey) { throw new InvalidOperationException($"BotState '{GetType().Name}' is being used by multiple conversations. Verify \"AddTransient\" DI registration."); } - return force || _cachedBotState == null || _cachedBotState.State == null; + return force || cachedState == null || cachedState.State == null; } /// @@ -247,11 +246,11 @@ public virtual void ClearState() { if (!IsLoaded()) { - throw new InvalidOperationException($"{_contextServiceKey} is not loaded"); + throw new InvalidOperationException($"{Name} is not loaded"); } // Explicitly setting the hash will mean IsChanged is always true. And that will force a Save. - _cachedBotState = new CachedBotState(_cachedBotState.Key) { Hash = string.Empty }; + GetCachedState().Clear(); } /// @@ -264,7 +263,7 @@ public virtual void ClearState() /// is null. public virtual async Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - if (_cachedBotState != null) + if (IsLoaded()) { ClearState(); } @@ -385,6 +384,12 @@ internal bool IsChanged() { return Hash != ComputeHash(State); } + + internal void Clear() + { + State = new Dictionary(); + Hash = string.Empty; + } } #region Obsolete BotStatePropertyAccessor @@ -439,7 +444,7 @@ public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancel public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) { T result = default(T); - + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); try diff --git a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs index 0a9a0ffc..dc162525 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Storage; using System; using System.Collections.Generic; using System.Linq; @@ -15,55 +16,86 @@ namespace Microsoft.Agents.State /// public class BotStateSet { - private IDictionary _scopes { get; set; } = new Dictionary(); + private readonly Dictionary _scopes = []; /// /// Initializes a new instance of the class. /// /// initial list of objects to manage. - public BotStateSet(params BotState[] botStates) + public BotStateSet(params IBotState[] botStates) { foreach (var botState in botStates) { - _scopes.Add(botState.ContextServiceKey, botState); + _scopes.Add(botState.Name, botState); } } - public T GetValue(ITurnContext turnContext, string name, Func defaultValueFactory) + /// + /// Creates BotStateSet with default ConversationState and UserState + /// + /// + /// Additional list of BotState objects to manage. + public BotStateSet(IStorage storage, params IBotState[] botStates) + { + _scopes.Add(ConversationState.ScopeName, new ConversationState(storage)); + _scopes.Add(UserState.ScopeName, new UserState(storage)); + _scopes.Add(TempState.ScopeName, new TempState()); + + foreach (var botState in botStates) + { + _scopes[botState.Name] = botState; + } + } + + public ConversationState Conversation => GetScope(); + public UserState User => GetScope(); + public PrivateConversationState Private => GetScope(); + public TempState Temp => GetScope(); + + public T GetValue(string name, Func defaultValueFactory = null) { var (scope, property) = GetScopeAndPath(name); return GetScope(scope).GetValue(property, defaultValueFactory); } - public void SetValue(ITurnContext turnContext, string name, object value) + public void SetValue(string name, object value) { var (scope, property) = GetScopeAndPath(name); GetScope(scope).SetValue(property, value); } - public BotState GetScope(string scope) + public void DeleteValue(string name) + { + var (scope, property) = GetScopeAndPath(name); + GetScope(scope).DeleteValue(property); + } + + public IBotState GetScope(string scope) { - if (!_scopes.TryGetValue(scope, out BotState value)) + if (!_scopes.TryGetValue(scope, out IBotState value)) { throw new ArgumentException($"Scope '{scope}' not found"); } return value; } - private (string, string) GetScopeAndPath(string name) + public T GetScope() { - var scopeEnd = name.IndexOf('.'); - if (scopeEnd == -1) + foreach (var scope in _scopes) { - throw new ArgumentException("Path must include the state scope name"); + if (scope.Value is T botState) + { + return botState; + } } - return (name.Substring(0, scopeEnd), name.Substring(scopeEnd + 1)); + + throw new ArgumentException($"Scope '{nameof(T)}' not found"); } - public BotStateSet Add(BotState botState) + public BotStateSet Add(IBotState botState) { ArgumentNullException.ThrowIfNull(botState); - _scopes.Add(botState.ContextServiceKey, botState); + _scopes.Add(botState.Name, botState); return this; } @@ -75,12 +107,17 @@ public BotStateSet Add(BotState botState) /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public async Task LoadAllAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + public async Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { var tasks = _scopes.Select(bs => bs.Value.LoadAsync(turnContext, force, cancellationToken)).ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); } + public void ClearState(string scope) + { + GetScope(scope).ClearState(); + } + /// /// Save All BotState changes in parallel. /// @@ -89,10 +126,20 @@ public async Task LoadAllAsync(ITurnContext turnContext, bool force = false, Can /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public async Task SaveAllChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + public async Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { var tasks = _scopes.Select(kv => kv.Value.SaveChangesAsync(turnContext, force, cancellationToken)).ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); } + + private static (string, string) GetScopeAndPath(string name) + { + var scopeEnd = name.IndexOf('.'); + if (scopeEnd == -1) + { + throw new ArgumentException("Path must include the state scope name"); + } + return (name.Substring(0, scopeEnd), name.Substring(scopeEnd + 1)); + } } } diff --git a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs index 84f3efd1..8f6a69fb 100644 --- a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs @@ -18,8 +18,10 @@ namespace Microsoft.Agents.State /// in DI. /// /// The storage layer to use. - public class ConversationState(IStorage storage) : BotState(storage, "conversation") + public class ConversationState(IStorage storage) : BotState(storage, ScopeName) { + public static readonly string ScopeName = "conversation"; + /// /// Gets the key to use when reading and writing state to and from storage. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs new file mode 100644 index 00000000..8f2a89bb --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Interfaces; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.State +{ + public interface IBotState + { + string Name { get; } + + void ClearState(); + Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); + void DeleteValue(string name); + T GetValue(string name, Func defaultValueFactory = null); + bool IsLoaded(); + Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + void SetValue(string name, T value); + } +} \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs index f98e6ecb..c7c0dfa4 100644 --- a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs @@ -18,8 +18,10 @@ namespace Microsoft.Agents.State /// in DI. /// /// The storage layer to use. - public class PrivateConversationState(IStorage storage) : BotState(storage, nameof(PrivateConversationState)) + public class PrivateConversationState(IStorage storage) : BotState(storage, ScopeName) { + public static readonly string ScopeName = "private"; + /// /// Gets the key to use when reading and writing state to and from storage. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/TempState.cs b/src/libraries/Core/Microsoft.Agents.State/TempState.cs new file mode 100644 index 00000000..7cce1825 --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/TempState.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.State +{ + public class TempState : IBotState + { + public static readonly string ScopeName = "temp"; + private readonly Dictionary _state = []; + + public const string AuthScopeKey = "AuthScope"; + public const string InvokeResponseKey = "InvokeResponse"; + public const string BotIdentityKey = "BotIdentity"; + + public string Name => ScopeName; + + public string AuthScope { get { return GetValue(AuthScopeKey); } set { SetValue(AuthScopeKey, value); } } + public ClaimsIdentity BotIdentity { get { return GetValue(BotIdentityKey); } set { SetValue(BotIdentityKey, value); } } + public IActivity InvokeResponse { get { return GetValue(InvokeResponseKey); } set { SetValue(InvokeResponseKey, value); } } + + public void ClearState() + { + _state.Clear(); + } + + public Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + _state.Clear(); + return Task.CompletedTask; + } + + public void DeleteValue(string name) + { + _state.Remove(name); + } + + public T GetValue(string name, Func defaultValueFactory = null) + { + T result = default; + + try + { + result = ObjectPath.GetPathValue(_state, name, true); + if (result == null) + { + if (defaultValueFactory != null) + { + result = defaultValueFactory(); + } + SetValue(name, result); + } + } + catch (KeyNotFoundException) + { + if (defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + SetValue(name, result); + } + } + + return result; + } + + public bool IsLoaded() + { + return true; + } + + public Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public void SetValue(string name, T value) + { + ObjectPath.SetPathValue(_state, name, value); + } + } +} diff --git a/src/libraries/Core/Microsoft.Agents.State/UserState.cs b/src/libraries/Core/Microsoft.Agents.State/UserState.cs index 34546685..d0a13feb 100644 --- a/src/libraries/Core/Microsoft.Agents.State/UserState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/UserState.cs @@ -18,8 +18,10 @@ namespace Microsoft.Agents.State /// in DI. /// /// The storage layer to use. - public class UserState(IStorage storage) : BotState(storage, "user") + public class UserState(IStorage storage) : BotState(storage, ScopeName) { + public static readonly string ScopeName = "user"; + /// /// Gets the key to use when reading and writing state to and from storage. /// diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index 350009df..075a936a 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -32,7 +32,7 @@ builder.Services.AddTransient(); -builder.Services.AddTransient((sp) => +builder.Services.AddSingleton((sp) => { return [ diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs index ab401d3c..7f5a9d09 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs @@ -15,17 +15,10 @@ public class BotStateSetTests public void TurnState_Properties() { var storage = new MemoryStorage(); + var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); - // setup userstate - var userState = new UserState(storage); - - // setup convState - var convState = new ConversationState(storage); - - var turnState = new BotStateSet(userState, convState); - - Assert.IsType(turnState.GetScope(userState.ContextServiceKey)); - Assert.IsType(turnState.GetScope(convState.ContextServiceKey)); + Assert.IsType(turnState.GetScope(UserState.ScopeName)); + Assert.IsType(turnState.GetScope(ConversationState.ScopeName)); } [Fact] @@ -35,47 +28,57 @@ public async Task TurnState_LoadAsync() var turnContext = TestUtilities.CreateEmptyContext(); { - // setup userstate - var userState = new UserState(storage); + var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + await turnState.LoadStateAsync(turnContext, false); - // setup convState - var convState = new ConversationState(storage); - - var turnState = new BotStateSet(userState, convState); - await turnState.LoadAllAsync(turnContext, false); - - var userCount = turnState.GetValue(turnContext, "user.userCount", () => 0); + var userCount = turnState.GetValue("user.userCount", () => 0); Assert.Equal(0, userCount); - var convCount = turnState.GetValue(turnContext, "conversation.convCount", () => 0); + var convCount = turnState.GetValue("conversation.convCount", () => 0); Assert.Equal(0, convCount); - turnState.SetValue(turnContext, "user.userCount", 10); - turnState.SetValue(turnContext, "conversation.convCount", 20); + turnState.SetValue("user.userCount", 10); + turnState.SetValue("conversation.convCount", 20); - Assert.Equal(10, turnState.GetValue(turnContext, "user.userCount", () => 0)); - Assert.Equal(20, turnState.GetValue(turnContext, "conversation.convCount", () => 0)); + Assert.Equal(10, turnState.GetValue("user.userCount", () => 0)); + Assert.Equal(20, turnState.GetValue("conversation.convCount", () => 0)); - await turnState.SaveAllChangesAsync(turnContext, false); + await turnState.SaveStateAsync(turnContext, false); } { - // setup userstate - var userState = new UserState(storage); - - // setup convState - var convState = new ConversationState(storage); - - var turnState = new BotStateSet(userState, convState); + var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); - await turnState.LoadAllAsync(turnContext); + await turnState.LoadStateAsync(turnContext); - var userCount = turnState.GetValue(turnContext, "user.userCount", () => 0); + var userCount = turnState.GetValue("user.userCount", () => 0); Assert.Equal(10, userCount); - var convCount = turnState.GetValue(turnContext, "conversation.convCount", () => 0); + var convCount = turnState.GetValue("conversation.convCount", () => 0); Assert.Equal(20, convCount); } } + [Fact] + public void TurnState_TempState() + { + var turnState = new BotStateSet(new TempState()); + + // Get should create property + var count = turnState.Temp.GetValue("count", () => 1); + Assert.Equal(1, count); + + // Get via path + count = turnState.GetValue("temp.count"); + Assert.Equal(1, count); + + turnState.Temp.SetValue("count", 2); + count = turnState.GetValue("temp.count"); + Assert.Equal(2, count); + + turnState.Temp.AuthScope = "botscope"; + Assert.Equal("botscope", turnState.Temp.AuthScope); + Assert.Equal("botscope", turnState.Temp.GetValue(TempState.AuthScopeKey)); + } + [Fact] public async Task TurnState_DottedProperties() { @@ -112,67 +115,64 @@ public async Task TurnState_DottedProperties() var turnContext = TestUtilities.CreateEmptyContext(); { - var userState = new UserState(storage); - var convState = new ConversationState(storage); - var turnState = new BotStateSet(userState, convState); - - await turnState.LoadAllAsync(turnContext, false); + var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + await turnState.LoadStateAsync(turnContext, false); // Add a couple array elements - turnState.SetValue(turnContext, "conversation.x.a[1]", "yabba"); - turnState.SetValue(turnContext, "conversation.x.a[0]", "dabba"); + turnState.SetValue("conversation.x.a[1]", "yabba"); + turnState.SetValue("conversation.x.a[0]", "dabba"); - Assert.Equal("dabba", turnState.GetValue(turnContext, "conversation.x.a[0]", () => string.Empty)); - Assert.Equal("yabba", turnState.GetValue(turnContext, "conversation.x.a[1]", () => string.Empty)); + Assert.Equal("dabba", turnState.GetValue("conversation.x.a[0]", () => string.Empty)); + Assert.Equal("yabba", turnState.GetValue("conversation.x.a[1]", () => string.Empty)); // Verify array - var array = turnState.GetValue(turnContext, "conversation.x.a", () => Array.Empty()); + var array = turnState.GetValue("conversation.x.a", () => Array.Empty()); Assert.IsAssignableFrom(array); Assert.Equal(2, array.Length); Assert.Equal("dabba", array[0]); Assert.Equal("yabba", array[1]); // Anonymous type access - turnState.SetValue(turnContext, "user.test", test); + turnState.SetValue("user.test", test); - Assert.Equal("FirstName", turnState.GetValue(turnContext, "user.test.bar.strIndex", () => string.Empty)); - Assert.Equal("Rex", turnState.GetValue(turnContext, "user.test.options.Nicknames[1]", () => string.Empty)); - Assert.Equal(2, turnState.GetValue(turnContext, "user.test.bar.numbers[1]", () => -1)); + Assert.Equal("FirstName", turnState.GetValue("user.test.bar.strIndex", () => string.Empty)); + Assert.Equal("Rex", turnState.GetValue("user.test.options.Nicknames[1]", () => string.Empty)); + Assert.Equal(2, turnState.GetValue("user.test.bar.numbers[1]", () => -1)); // Anonymous types are read only - Assert.Throws(() => turnState.SetValue(turnContext, "user.test.bar.strIndex", "NewFirstName")); + Assert.Throws(() => turnState.SetValue("user.test.bar.strIndex", "NewFirstName")); // Don't support growing native arrays yet - Assert.Throws(() => turnState.SetValue(turnContext, "user.test.bar.numbers[10]", 10)); + Assert.Throws(() => turnState.SetValue("user.test.bar.numbers[10]", 10)); // But it can grow lists - turnState.SetValue(turnContext, "user.test.options.Nicknames[5]", "John"); - Assert.Equal("John", turnState.GetValue(turnContext, "user.test.options.Nicknames[5]", () => string.Empty)); + turnState.SetValue("user.test.options.Nicknames[5]", "John"); + Assert.Equal("John", turnState.GetValue("user.test.options.Nicknames[5]", () => string.Empty)); // Can set poco var poco = new TestPocoState() { Value = "firstValue" }; - turnState.SetValue(turnContext, "user.poco", poco); - Assert.Equal("firstValue", turnState.GetValue(turnContext, "user.poco.Value", () => string.Empty)); + turnState.SetValue("user.poco", poco); + Assert.Equal("firstValue", turnState.GetValue("user.poco.Value", () => string.Empty)); // Get poco object - var pocoOut = turnState.GetValue(turnContext, "user.poco", () => new TestPocoState()); + var pocoOut = turnState.GetValue("user.poco", () => new TestPocoState()); Assert.Equal("firstValue", pocoOut.Value); // Can set poco field - turnState.SetValue(turnContext, "user.poco.Value", "secondValue"); - Assert.Equal("secondValue", turnState.GetValue(turnContext, "user.poco.Value", () => string.Empty)); + turnState.SetValue("user.poco.Value", "secondValue"); + Assert.Equal("secondValue", turnState.GetValue("user.poco.Value", () => string.Empty)); // List - turnState.SetValue(turnContext, "conversation.chatHistory", new List()); - var chatHistory = turnState.GetValue(turnContext, "conversation.chatHistory", () => new List()); + turnState.SetValue("conversation.chatHistory", new List()); + var chatHistory = turnState.GetValue("conversation.chatHistory", () => new List()); chatHistory.Add("Hello"); chatHistory.Add("Howdy"); - Assert.Equal("Hello", turnState.GetValue(turnContext, "conversation.chatHistory[0]", () => string.Empty)); - Assert.Equal("Howdy", turnState.GetValue(turnContext, "conversation.chatHistory[1]", () => string.Empty)); + Assert.Equal("Hello", turnState.GetValue("conversation.chatHistory[0]", () => string.Empty)); + Assert.Equal("Howdy", turnState.GetValue("conversation.chatHistory[1]", () => string.Empty)); } } @@ -180,30 +180,22 @@ public async Task TurnState_DottedProperties() public async Task TurnState_ReturnsDefaultForNullValueType() { var storage = new MemoryStorage(); - var turnContext = TestUtilities.CreateEmptyContext(); + var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + await turnState.LoadStateAsync(turnContext, false); - // setup userstate - var userState = new UserState(storage); - - // setup convState - var convState = new ConversationState(storage); - - var turnState = new BotStateSet(userState, convState); - await turnState.LoadAllAsync(turnContext, false); - - var userObject = turnState.GetValue(turnContext, "user.userStateObject", () => null); + var userObject = turnState.GetValue("user.userStateObject"); Assert.Null(userObject); // Ensure we also get null on second attempt - userObject = turnState.GetValue(turnContext, "user.userStateObject", () => null); + userObject = turnState.GetValue("user.userStateObject"); Assert.Null(userObject); - var convObject = turnState.GetValue(turnContext, "conversation.convStateObject", () => null); + var convObject = turnState.GetValue("conversation.convStateObject"); Assert.Null(convObject); // Ensure we also get null on second attempt - convObject = turnState.GetValue(turnContext, "conversation.convStateObject", () => null); + convObject = turnState.GetValue("conversation.convStateObject"); Assert.Null(convObject); } @@ -221,7 +213,7 @@ public async Task TurnState_SaveAsync() var turnState = new BotStateSet(userState, convState); var context = TestUtilities.CreateEmptyContext(); - await turnState.LoadAllAsync(context); + await turnState.LoadStateAsync(context); var userCount = userState.GetValue("userCount", () => 0); Assert.Equal(0, userCount); @@ -231,7 +223,7 @@ public async Task TurnState_SaveAsync() userState.SetValue("userCount", 10); convState.SetValue("convCount", 20); - await turnState.SaveAllChangesAsync(context); + await turnState.SaveStateAsync(context); userCount = userState.GetValue("userCount", () => 0); Assert.Equal(10, userCount); diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs index 5b724631..77d52ba3 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs @@ -47,6 +47,31 @@ public async Task State_NullName() Assert.Throws(() => userState.GetValue(null, () => string.Empty)); } + [Fact] + public void State_WithDefaultBotState() + { + var storage = new MemoryStorage(); + + var turnState = new BotStateSet(storage); + + Assert.IsAssignableFrom(turnState.Conversation); + Assert.IsAssignableFrom(turnState.User); + Assert.IsAssignableFrom(turnState.Temp); + Assert.Throws(() => turnState.Private); + + turnState = new BotStateSet(storage, new PrivateConversationState(storage)); + Assert.IsAssignableFrom(turnState.Conversation); + Assert.IsAssignableFrom(turnState.User); + Assert.IsAssignableFrom(turnState.Temp); + Assert.IsAssignableFrom(turnState.GetScope()); + + turnState = new BotStateSet(new UserState(storage), new ConversationState(storage), new TempState()); + Assert.IsAssignableFrom(turnState.Conversation); + Assert.IsAssignableFrom(turnState.User); + Assert.IsAssignableFrom(turnState.Temp); + Assert.Throws(() => turnState.Private); + } + [Fact] public async Task State_WriteAsyncStoreItem() { @@ -581,12 +606,12 @@ public async Task State_UseBotStateDirectly() adapter, async (context, cancellationToken) => { - var botStateManager = new TestBotState(new MemoryStorage()); + var testState = new TestBotState(new MemoryStorage()); // read initial state object - await botStateManager.LoadAsync(context); + await testState.LoadAsync(context); - var customState = botStateManager.GetValue("test", () => new CustomState()); + var customState = testState.GetValue("test", () => new CustomState()); // this should be a 'new CustomState' as nothing is currently stored in storage Assert.NotNull(customState); @@ -595,14 +620,14 @@ public async Task State_UseBotStateDirectly() // amend property and write to storage customState.CustomString = "test"; - await botStateManager.SaveChangesAsync(context); + await testState.SaveChangesAsync(context, false, cancellationToken); customState.CustomString = "asdfsadf"; // read into context again - await botStateManager.LoadAsync(context, force: true); + await testState.LoadAsync(context, force: true, cancellationToken); - customState = botStateManager.GetValue("test", () => new CustomState()); + customState = testState.GetValue("test", () => new CustomState()); // check object read from value has the correct value for CustomString Assert.Equal("test", customState.CustomString); From 4d87e8f79b9f076bb1832560c0be89cb8dae2e83 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 28 Jan 2025 18:15:25 -0600 Subject: [PATCH 03/60] Teams AI Application initial merge --- Directory.Packages.props | 1 + src/Microsoft.Agents.SDK.sln | 12 + .../Application/ActivityUtilities.cs | 24 + .../AdaptiveCardInvokeResponseFactory.cs | 41 + .../AdaptiveCards/AdaptiveCards.cs | 460 +++++++ .../AdaptiveCards/AdaptiveCardsHandlers.cs | 99 ++ .../AdaptiveCards/AdaptiveCardsOptions.cs | 18 + .../Application/Application.cs | 1098 +++++++++++++++++ .../Application/ApplicationBuilder.cs | 125 ++ .../Application/ApplicationOptions.cs | 80 ++ .../Application/ConfigHandlerAsync.cs | 20 + .../Application/ConversationUpdateEvents.cs | 78 ++ .../Application/Exceptions/AuthException.cs | 46 + .../Exceptions/InvokeResponseException.cs | 34 + .../Exceptions/TeamsAIException.cs | 28 + .../Application/FeedbackLoopData.cs | 40 + .../Application/FeedbackLoopHandler.cs | 19 + .../Application/FileConsentCardHandler.cs | 20 + .../Application/HandoffHandler.cs | 19 + .../Application/InputFile.cs | 42 + .../Application/Meetings/Meetings.cs | 120 ++ .../Application/Meetings/MeetingsHandlers.cs | 45 + .../MessageExtensions/MessageExtensions.cs | 793 ++++++++++++ .../MessageExtensionsHandlers.cs | 128 ++ .../O365ConnectorCardActionHandler.cs | 20 + .../Application/Query.cs | 37 + .../Application/ReadReceiptHandler.cs | 19 + .../Route/MultipleRouteSelector.cs | 25 + .../Application/Route/Route.cs | 57 + .../Application/State/IMemory.cs | 39 + .../Application/State/ITurnState.cs | 51 + .../Application/State/MemoryFork.cs | 114 ++ .../Application/State/Record.cs | 81 ++ .../Application/State/TempState.cs | 113 ++ .../Application/State/TurnState.cs | 474 +++++++ .../Application/State/TurnStateEntry.cs | 87 ++ .../Application/StreamingResponse.cs | 324 +++++ .../Application/TaskModules/TaskModules.cs | 273 ++++ .../TaskModules/TaskModulesHandlers.cs | 31 + .../TaskModules/TaskModulesOptions.cs | 18 + .../Application/TurnEventHandlerAsync.cs | 26 + .../Application/TypingTimer.cs | 139 +++ .../Application/Verify.cs | 39 + .../Microsoft.Agents.BotBuilder.csproj | 2 + .../Models/SearchInvokeValue.cs | 4 +- .../messaging.echoBot/AspNetExtensions.cs | 207 ++++ .../messaging.echoBot/BotController.cs | 25 + .../messaging.echoBot/EchoBot.csproj | 24 + .../Application/messaging.echoBot/EchoBot.sln | 27 + .../messaging.echoBot/EchoBotApplication.cs | 60 + .../messaging.echoBot/Model/AppState.cs | 53 + .../Application/messaging.echoBot/Program.cs | 64 + .../Application/messaging.echoBot/README.md | 29 + .../messaging.echoBot/appsettings.json | 38 + 54 files changed, 5888 insertions(+), 2 deletions(-) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs create mode 100644 src/samples/Application/messaging.echoBot/AspNetExtensions.cs create mode 100644 src/samples/Application/messaging.echoBot/BotController.cs create mode 100644 src/samples/Application/messaging.echoBot/EchoBot.csproj create mode 100644 src/samples/Application/messaging.echoBot/EchoBot.sln create mode 100644 src/samples/Application/messaging.echoBot/EchoBotApplication.cs create mode 100644 src/samples/Application/messaging.echoBot/Model/AppState.cs create mode 100644 src/samples/Application/messaging.echoBot/Program.cs create mode 100644 src/samples/Application/messaging.echoBot/README.md create mode 100644 src/samples/Application/messaging.echoBot/appsettings.json diff --git a/Directory.Packages.props b/Directory.Packages.props index cea2151e..0f89aa1d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -43,6 +43,7 @@ + diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index cb02dc7f..7e791988 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -128,6 +128,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InMeetingNotificationsBot", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagMentionBot", "samples\Teams\bot-tag-mention\TagMentionBot.csproj", "{BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{A2AEDED1-5F24-4084-BB93-8B3E3D490FFA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{CB471290-FA93-4109-8A65-37F0DE9EDB2C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -330,6 +334,12 @@ Global {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Release|Any CPU.Build.0 = Release|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.Build.0 = Release|Any CPU + {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -393,6 +403,8 @@ Global {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {06E490F7-F0BB-E3C4-54FE-5210627292A1} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} + {A2AEDED1-5F24-4084-BB93-8B3E3D490FFA} = {674A812C-7287-4883-97F9-697D83750648} + {CB471290-FA93-4109-8A65-37F0DE9EDB2C} = {A2AEDED1-5F24-4084-BB93-8B3E3D490FFA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs new file mode 100644 index 00000000..2a00298f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs @@ -0,0 +1,24 @@ +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System.Net; + +namespace Microsoft.Teams.AI +{ + internal static class ActivityUtilities + { + public static T? GetTypedValue(IActivity activity) + { + return ProtocolJsonSerializer.ToObject(activity.Value); + } + + public static Activity CreateInvokeResponseActivity(object? body = default) + { + Activity activity = new() + { + Type = ActivityTypes.InvokeResponse, + Value = new InvokeResponse { Status = (int)HttpStatusCode.OK, Body = body } + }; + return activity; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs new file mode 100644 index 00000000..7d88b439 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs @@ -0,0 +1,41 @@ +using AdaptiveCards; +using Microsoft.Agents.Core.Models; + +namespace Microsoft.Teams.AI +{ + /// + /// Contains utility methods for creating various types of . + /// + public static class AdaptiveCardInvokeResponseFactory + { + /// + /// Returns response with type "application/vnd.microsoft.card.adaptive". + /// + /// An Adaptive Card. + /// The response that includes an Adaptive Card that the client should display. + public static AdaptiveCardInvokeResponse AdaptiveCard(AdaptiveCard adaptiveCard) + { + return new AdaptiveCardInvokeResponse + { + StatusCode = 200, + Type = "application/vnd.microsoft.card.adaptive", + Value = adaptiveCard + }; + } + + /// + /// Returns response with type "application/vnd.microsoft.activity.message". + /// + /// A message. + /// The response that includes a message that the client should display. + public static AdaptiveCardInvokeResponse Message(string message) + { + return new AdaptiveCardInvokeResponse + { + StatusCode = 200, + Type = "application/vnd.microsoft.activity.message", + Value = message + }; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs new file mode 100644 index 00000000..84426ef4 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs @@ -0,0 +1,460 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Constants for adaptive card invoke names + /// + public class AdaptiveCardsInvokeNames + { + /// + /// Action invoke name + /// + public static readonly string ACTION_INVOKE_NAME = "adaptiveCard/action"; + } + + /// + /// AdaptiveCards class to enable fluent style registration of handlers related to Adaptive Cards. + /// + /// The type of the turn state object used by the application. + public class AdaptiveCards + where TState : TurnState, new() + { + private static readonly string ACTION_EXECUTE_TYPE = "Action.Execute"; + private static readonly string SEARCH_INVOKE_NAME = "application/search"; + private static readonly string DEFAULT_ACTION_SUBMIT_FILTER = "verb"; + + private readonly Application _app; + + /// + /// Creates a new instance of the AdaptiveCards class. + /// + /// The top level application class to register handlers with. + public AdaptiveCards(Application app) + { + this._app = app; + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Execute events. + /// + /// The named action to be handled. + /// Function to call when the action is triggered. + /// The application instance for chaining purposes. + public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handler) + { + Verify.ParamNotNull(verb); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateActionExecuteSelector((string input) => string.Equals(verb, input)); + return OnActionExecute(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Execute events. + /// + /// Regular expression to match against the named action to be handled. + /// Function to call when the action is triggered. + /// The application instance for chaining purposes. + public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) + { + Verify.ParamNotNull(verbPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateActionExecuteSelector((string input) => verbPattern.IsMatch(input)); + return OnActionExecute(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Execute events. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + AdaptiveCardInvokeValue? invokeValue; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, AdaptiveCardsInvokeNames.ACTION_INVOKE_NAME) + || (invokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || invokeValue.Action == null + || !string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE)) + { + throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: {turnContext.Activity.Type}"); + } + + AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = await handler(turnContext, turnState, invokeValue.Action.Data, cancellationToken); + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(adaptiveCardInvokeResponse); + await turnContext.SendActivityAsync(activity, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Execute events. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string verb in routeSelectors.Strings) + { + OnActionExecute(verb, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex verbPattern in routeSelectors.Regexes) + { + OnActionExecute(verbPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnActionExecute(routeSelector, handler); + } + } + return _app; + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Submit events. + /// + /// + /// The route will be added for the specified verb(s) and will be filtered using the + /// `actionSubmitFilter` option. The default filter is to use the `verb` field. + /// + /// For outgoing AdaptiveCards you will need to include the verb's name in the cards Action.Submit. + /// For example: + /// + /// ```JSON + /// { + /// "type": "Action.Submit", + /// "title": "OK", + /// "data": { + /// "verb": "ok" + /// } + /// } + /// ``` + /// + /// The named action to be handled. + /// Function to call when the action is triggered. + /// The application instance for chaining purposes. + public Application OnActionSubmit(string verb, ActionSubmitHandler handler) + { + Verify.ParamNotNull(verb); + Verify.ParamNotNull(handler); + string filter = _app.Options.AdaptiveCards?.ActionSubmitFilter ?? DEFAULT_ACTION_SUBMIT_FILTER; + RouteSelectorAsync routeSelector = CreateActionSubmitSelector((string input) => string.Equals(verb, input), filter); + return OnActionSubmit(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Submit events. + /// + /// + /// The route will be added for the specified verb(s) and will be filtered using the + /// `actionSubmitFilter` option. The default filter is to use the `verb` field. + /// + /// For outgoing AdaptiveCards you will need to include the verb's name in the cards Action.Submit. + /// For example: + /// + /// ```JSON + /// { + /// "type": "Action.Submit", + /// "title": "OK", + /// "data": { + /// "verb": "ok" + /// } + /// } + /// ``` + /// + /// Regular expression to match against the named action to be handled. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) + { + Verify.ParamNotNull(verbPattern); + Verify.ParamNotNull(handler); + string filter = _app.Options.AdaptiveCards?.ActionSubmitFilter ?? DEFAULT_ACTION_SUBMIT_FILTER; + RouteSelectorAsync routeSelector = CreateActionSubmitSelector((string input) => verbPattern.IsMatch(input), filter); + return OnActionSubmit(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Submit events. + /// + /// + /// The route will be added for the specified verb(s) and will be filtered using the + /// `actionSubmitFilter` option. The default filter is to use the `verb` field. + /// + /// For outgoing AdaptiveCards you will need to include the verb's name in the cards Action.Submit. + /// For example: + /// + /// ```JSON + /// { + /// "type": "Action.Submit", + /// "title": "OK", + /// "data": { + /// "verb": "ok" + /// } + /// } + /// ``` + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) + || !string.IsNullOrEmpty(turnContext.Activity.Text) + || turnContext.Activity.Value == null) + { + throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionSubmit() triggered for activity type: {turnContext.Activity.Type}"); + } + + await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return _app; + } + + /// + /// Adds a route to the application for handling Adaptive Card Action.Submit events. + /// + /// + /// The route will be added for the specified verb(s) and will be filtered using the + /// `actionSubmitFilter` option. The default filter is to use the `verb` field. + /// + /// For outgoing AdaptiveCards you will need to include the verb's name in the cards Action.Submit. + /// For example: + /// + /// ```JSON + /// { + /// "type": "Action.Submit", + /// "title": "OK", + /// "data": { + /// "verb": "ok" + /// } + /// } + /// ``` + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string verb in routeSelectors.Strings) + { + OnActionSubmit(verb, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex verbPattern in routeSelectors.Regexes) + { + OnActionSubmit(verbPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnActionSubmit(routeSelector, handler); + } + } + return _app; + } + + /// + /// Adds a route to the application for handling Adaptive Card dynamic search events. + /// + /// The dataset to be searched. + /// Function to call when the search is triggered. + /// The application instance for chaining purposes. + public Application OnSearch(string dataset, SearchHandlerAsync handler) + { + Verify.ParamNotNull(dataset); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateSearchSelector((string input) => string.Equals(dataset, input)); + return OnSearch(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card dynamic search events. + /// + /// Regular expression to match against the dataset to be searched. + /// Function to call when the search is triggered. + /// The application instance for chaining purposes. + public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) + { + Verify.ParamNotNull(datasetPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateSearchSelector((string input) => datasetPattern.IsMatch(input)); + return OnSearch(routeSelector, handler); + } + + /// + /// Adds a route to the application for handling Adaptive Card dynamic search events. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + AdaptiveCardSearchInvokeValue? searchInvokeValue; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME) + || (searchInvokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + { + throw new TeamsAIException($"Unexpected AdaptiveCards.OnSearch() triggered for activity type: {turnContext.Activity.Type}"); + } + + AdaptiveCardsSearchParams adaptiveCardsSearchParams = new(searchInvokeValue.QueryText, searchInvokeValue.Dataset ?? string.Empty); + Query query = new(searchInvokeValue.QueryOptions.Top, searchInvokeValue.QueryOptions.Skip, adaptiveCardsSearchParams); + IList results = await handler(turnContext, turnState, query, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + SearchInvokeResponse searchInvokeResponse = new() + { + StatusCode = 200, + Type = "application/vnd.microsoft.search.searchResponse", + Value = new AdaptiveCardsSearchInvokeResponseValue + { + Results = results + } + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(searchInvokeResponse); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Adds a route to the application for handling Adaptive Card dynamic search events. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string verb in routeSelectors.Strings) + { + OnSearch(verb, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex verbPattern in routeSelectors.Regexes) + { + OnSearch(verbPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnSearch(routeSelector, handler); + } + } + return _app; + } + + private static RouteSelectorAsync CreateActionExecuteSelector(Func isMatch) + { + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + AdaptiveCardInvokeValue? invokeValue; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, AdaptiveCardsInvokeNames.ACTION_INVOKE_NAME) + && (invokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) != null + && invokeValue.Action != null + && string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE) + && isMatch(invokeValue.Action.Verb)); + }; + return routeSelector; + } + + private static RouteSelectorAsync CreateActionSubmitSelector(Func isMatch, string filter) + { + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + JsonObject obj = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) + && string.IsNullOrEmpty(turnContext.Activity.Text) + && turnContext.Activity.Value != null + && (obj = turnContext.Activity.Value as JsonObject) != null + && obj[filter] != null + && obj[filter]!.GetValueKind() == System.Text.Json.JsonValueKind.String + && isMatch(obj[filter]!.ToString()!)); + }; + return routeSelector; + } + + private static RouteSelectorAsync CreateSearchSelector(Func isMatch) + { + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + AdaptiveCardSearchInvokeValue searchInvokeValue = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME) + && (searchInvokeValue != null + && isMatch(searchInvokeValue.Dataset!))); + }; + return routeSelector; + } + + private class AdaptiveCardSearchInvokeValue : SearchInvokeValue + { + public string? Dataset { get; set; } + } + + private class AdaptiveCardsSearchInvokeResponseValue + { + public IList? Results { get; set; } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs new file mode 100644 index 00000000..abca7647 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs @@ -0,0 +1,99 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Teams.AI.State; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Parameters passed to AdaptiveCards.OnSearch() handler. + /// + public class AdaptiveCardsSearchParams + { + /// + /// The query text. + /// + public string QueryText { get; set; } + + /// + /// The dataset to search. + /// + public string Dataset { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The query text. + /// The dataset to search. + public AdaptiveCardsSearchParams(string queryText, string dataset) + { + this.QueryText = queryText; + this.Dataset = dataset; + } + } + + /// + /// Individual result returned from AdaptiveCards.OnSearch() handler. + /// + public class AdaptiveCardsSearchResult + { + /// + /// The title of the result. + /// + public string Title { get; set; } + + /// + /// The subtitle of the result. + /// + public string Value { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The title of the result. + /// The subtitle of the result. + public AdaptiveCardsSearchResult(string title, string value) + { + this.Title = title; + this.Value = value; + } + } + + /// + /// Function for handling Adaptive Card Action.Execute events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data associated with the action. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of AdaptiveCardInvokeResponse, which can be created using . + public delegate Task ActionExecuteHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Adaptive Card Action.Submit events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data associated with the action. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task ActionSubmitHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Adaptive Card dynamic search events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The query arguments. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A list of AdaptiveCardsSearchResult. + public delegate Task> SearchHandlerAsync(ITurnContext turnContext, TState turnState, Query query, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs new file mode 100644 index 00000000..dead50f6 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs @@ -0,0 +1,18 @@ +namespace Microsoft.Teams.AI +{ + /// + /// Options for AdaptiveCards class. + /// + public class AdaptiveCardsOptions + { + /// + /// Data field used to identify the Action.Submit handler to trigger. + /// + /// + /// When an Action.Submit is triggered, the field name specified here will be used to determine + /// the handler to route the request to. + /// Defaults to a value of "verb". + /// + public string? ActionSubmitFilter { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs new file mode 100644 index 00000000..9c6e1934 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs @@ -0,0 +1,1098 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Storage; +using Microsoft.Teams.AI.Application; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Concurrent; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Application class for routing and processing incoming requests. + /// + /// + /// The Application object replaces the traditional ActivityHandler that a bot would use. It supports + /// a simpler fluent style of authoring bots versus the inheritance based approach used by the + /// ActivityHandler class. + /// + /// Additionally, it has built-in support for calling into the SDK's AI system and can be used to create + /// bots that leverage Large Language Models (LLM) and other AI capabilities. + /// + /// Type of the turnState. This allows for strongly typed access to the turn turnState. + public class Application : IBot + where TState : TurnState, new() + { + private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; + private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; + + private readonly ChannelAdapter? _adapter; + //TODO + //private readonly AuthenticationManager? _authentication; + + private readonly int _typingTimerDelay = 1000; + private TypingTimer? _typingTimer; + + private readonly ConcurrentQueue> _invokeRoutes; + private readonly ConcurrentQueue> _routes; + + private readonly ConcurrentQueue> _beforeTurn; + private readonly ConcurrentQueue> _afterTurn; + + // TODO + //private readonly SelectorAsync? _startSignIn; + + /// + /// Creates a new Application instance. + /// + /// Optional. Options used to configure the application. + public Application(ApplicationOptions options) + { + Verify.ParamNotNull(options); + + Options = options; + + if (Options.TurnStateFactory == null) + { + this.Options.TurnStateFactory = () => new TState(); + } + + if (Options.Adapter != null) + { + _adapter = Options.Adapter; + } + + AdaptiveCards = new AdaptiveCards(this); + Meetings = new Meetings(this); + MessageExtensions = new MessageExtensions(this); + TaskModules = new TaskModules(this); + + _routes = new ConcurrentQueue>(); + _invokeRoutes = new ConcurrentQueue>(); + _beforeTurn = new ConcurrentQueue>(); + _afterTurn = new ConcurrentQueue>(); + + //TODO + /* + if (options.Authentication != null) + { + _authentication = new AuthenticationManager(this, options.Authentication, options.Storage); + + if (options.Authentication.AutoSignIn != null) + { + _startSignIn = options.Authentication.AutoSignIn; + } + else + { + _startSignIn = (context, cancellationToken) => Task.FromResult(true); + } + } + */ + } + + /// + /// Fluent interface for accessing Adaptive Card specific features. + /// + public AdaptiveCards AdaptiveCards { get; } + + /// + /// Fluent interface for accessing Meetings' specific features. + /// + public Meetings Meetings { get; } + + /// + /// Fluent interface for accessing Message Extensions' specific features. + /// + public MessageExtensions MessageExtensions { get; } + + /// + /// Fluent interface for accessing Task Modules' specific features. + /// + public TaskModules TaskModules { get; } + + //TODO + /* + /// + /// Accessing authentication specific features. + /// + public AuthenticationManager Authentication + { + + get + { + if (_authentication == null) + { + throw new ArgumentException("The Application.Authentication property is unavailable because no authentication options were configured."); + } + + return _authentication; + } + } + */ + + /// + /// Fluent interface for accessing the bot adapter used to configure the application. + /// + public ChannelAdapter Adapter + { + get + { + if (_adapter == null) + { + throw new ArgumentException("The Application.Adapter property is unavailable because it was not configured."); + } + + return _adapter; + } + } + + /// + /// The application's configured options. + /// + public ApplicationOptions Options { get; } + + /// + /// Adds a new route to the application. + /// + /// Developers won't typically need to call this method directly as it's used internally by all + /// of the fluent interfaces to register routes for their specific activity types. + /// + /// Routes will be matched in the order they're added to the application. The first selector to + /// return `true` when an activity is received will have its handler called. + /// + /// Invoke-based activities receive special treatment and are matched separately as they typically + /// have shorter execution timeouts. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// Boolean indicating if the RouteSelectorAsync is for an activity that uses "invoke" which require special handling. Defaults to `false`. + /// The application instance for chaining purposes. + public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) + { + Verify.ParamNotNull(selector); + Verify.ParamNotNull(handler); + Route route = new(selector, handler, isInvokeRoute); + if (isInvokeRoute) + { + _invokeRoutes.Enqueue(route); + } + else + { + _routes.Enqueue(route); + } + return this; + } + + /// + /// Handles incoming activities of a given type. + /// + /// Name of the activity type to match. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActivity(string type, RouteHandler handler) + { + Verify.ParamNotNull(type); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(string.Equals(type, context.Activity?.Type, StringComparison.OrdinalIgnoreCase)); + OnActivity(routeSelector, handler); + return this; + } + + /// + /// Handles incoming activities of a given type. + /// + /// Regular expression to match against the incoming activity type. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActivity(Regex typePattern, RouteHandler handler) + { + Verify.ParamNotNull(typePattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(context.Activity?.Type != null && typePattern.IsMatch(context.Activity?.Type)); + OnActivity(routeSelector, handler); + return this; + } + + /// + /// Handles incoming activities of a given type. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles incoming activities of a given type. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string type in routeSelectors.Strings) + { + OnActivity(type, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex typePattern in routeSelectors.Regexes) + { + OnActivity(typePattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnActivity(routeSelector, handler); + } + } + return this; + } + + /// + /// Handles conversation update events. + /// + /// Name of the conversation update event to handle, can use . + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + { + Verify.ParamNotNull(conversationUpdateEvent); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector; + switch (conversationUpdateEvent) + { + case ConversationUpdateEvents.ChannelCreated: + case ConversationUpdateEvents.ChannelDeleted: + case ConversationUpdateEvents.ChannelRenamed: + case ConversationUpdateEvents.ChannelRestored: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + && context.Activity?.GetChannelData()?.Channel != null + && context.Activity?.GetChannelData()?.Team != null + ); + break; + } + case ConversationUpdateEvents.MembersAdded: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersAdded != null + && context.Activity.MembersAdded.Count > 0 + ); + break; + } + case ConversationUpdateEvents.MembersRemoved: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersRemoved != null + && context.Activity.MembersRemoved.Count > 0 + ); + break; + } + case ConversationUpdateEvents.TeamRenamed: + case ConversationUpdateEvents.TeamDeleted: + case ConversationUpdateEvents.TeamHardDeleted: + case ConversationUpdateEvents.TeamArchived: + case ConversationUpdateEvents.TeamUnarchived: + case ConversationUpdateEvents.TeamRestored: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + && context.Activity?.GetChannelData()?.Team != null + ); + break; + } + default: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + ); + break; + } + } + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles conversation update events. + /// + /// Name of the conversation update events to handle, can use as array item. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler) + { + Verify.ParamNotNull(conversationUpdateEvents); + Verify.ParamNotNull(handler); + foreach (string conversationUpdateEvent in conversationUpdateEvents) + { + OnConversationUpdate(conversationUpdateEvent, handler); + } + return this; + } + + /// + /// Handles incoming messages with a given keyword. + ///
+ /// This method provides a simple way to have a bot respond anytime a user sends your bot a + /// message with a specific word or phrase. + ///
+ /// For example, you can easily clear the current conversation anytime a user sends "/reset": + ///
+ /// application.OnMessage("/reset", (context, turnState, _) => ...); + ///
+ /// Substring of the incoming message text. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessage(string text, RouteHandler handler) + { + Verify.ParamNotNull(text); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) + => Task.FromResult + ( + string.Equals(ActivityTypes.Message, context.Activity?.Type, StringComparison.OrdinalIgnoreCase) + && context.Activity?.Text != null + && context.Activity.Text.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 + ); + OnMessage(routeSelector, handler); + return this; + } + + /// + /// Handles incoming messages with a given keyword. + ///
+ /// This method provides a simple way to have a bot respond anytime a user sends your bot a + /// message with a specific word or phrase. + ///
+ /// For example, you can easily clear the current conversation anytime a user sends "/reset": + ///
+ /// application.OnMessage(new Regex("reset"), (context, turnState, _) => ...); + ///
+ /// Regular expression to match against the text of an incoming message. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessage(Regex textPattern, RouteHandler handler) + { + Verify.ParamNotNull(textPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) + => Task.FromResult + ( + string.Equals(ActivityTypes.Message, context.Activity?.Type, StringComparison.OrdinalIgnoreCase) + && context.Activity?.Text != null + && textPattern.IsMatch(context.Activity.Text) + ); + OnMessage(routeSelector, handler); + return this; + } + + /// + /// Handles incoming messages with a given keyword. + ///
+ /// This method provides a simple way to have a bot respond anytime a user sends your bot a + /// message with a specific word or phrase. + ///
+ /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles incoming messages with a given keyword. + ///
+ /// This method provides a simple way to have a bot respond anytime a user sends your bot a + /// message with a specific word or phrase. + ///
+ /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string text in routeSelectors.Strings) + { + OnMessage(text, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex textPattern in routeSelectors.Regexes) + { + OnMessage(textPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnMessage(routeSelector, handler); + } + } + return this; + } + + /// + /// Handles message edit events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageEdit(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "editMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message undo soft delete events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageUndelete(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "undeleteMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message soft delete events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageDelete(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageDelete, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "softDeleteMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message reactions added events. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessageReactionsAdded(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.MessageReaction, StringComparison.OrdinalIgnoreCase) + && context.Activity?.ReactionsAdded != null + && context.Activity.ReactionsAdded.Count > 0 + ); + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message reactions removed events. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnMessageReactionsRemoved(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.MessageReaction, StringComparison.OrdinalIgnoreCase) + && context.Activity?.ReactionsRemoved != null + && context.Activity.ReactionsRemoved.Count > 0 + ); + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles read receipt events for messages sent by the bot in personal scope. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnTeamsReadReceipt(ReadReceiptHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.readReceipt") + ); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ReadReceiptInfo readReceiptInfo = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, readReceiptInfo, cancellationToken); + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return this; + } + + /// + /// Handles config fetch events for Microsoft Teams. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnConfigFetch(ConfigHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, CONFIG_FETCH_INVOKE_NAME) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles config submit events for Microsoft Teams. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnConfigSubmit(ConfigHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, CONFIG_SUBMIT_INVOKE_NAME) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles when a file consent card is accepted by the user. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFileConsentAccept(FileConsentHandler handler) + => OnFileConsent(handler, "accept"); + + /// + /// Handles when a file consent card is declined by the user. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFileConsentDecline(FileConsentHandler handler) + => OnFileConsent(handler, "decline"); + + private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => + { + FileConsentCardResponse? fileConsentCardResponse; + return Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "fileConsent/invoke") + && (fileConsentCardResponse = ActivityUtilities.GetTypedValue(context.Activity!)) != null + && string.Equals(fileConsentCardResponse.Action, fileConsentAction) + ); + }; + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + FileConsentCardResponse fileConsentCardResponse = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, fileConsentCardResponse, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles O365 Connector Card Action activities. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "actionableMessage/executeAction") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + O365ConnectorCardActionQuery query = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, query, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles handoff activities. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnHandoff(HandoffHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "handoff/action") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + string token = turnContext.Activity.Value.GetType().GetProperty("Continuation").GetValue(turnContext.Activity.Value) as string ?? ""; + await handler(turnContext, turnState, token, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Registers a handler for feedback loop events when a user clicks the thumbsup or thumbsdown button on a response sent from the AI module. + /// must be set to true. + /// + /// Function to cal lwhen the route is triggered + /// + public Application OnFeedbackLoop(FeedbackLoopHandler handler) + { + Verify.ParamNotNull(handler); + + RouteSelectorAsync routeSelector = (context, _) => + { + var jsonObject = ProtocolJsonSerializer.ToObject(context.Activity.Value); + string? actionName = jsonObject.ContainsKey("actionName") ? jsonObject["actionName"].ToString() : string.Empty; + return Task.FromResult + ( + context.Activity.Type == ActivityTypes.Invoke + && context.Activity.Name == "message/submitAction" + && actionName == "feedback" + ); + }; + + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + FeedbackLoopData feedbackLoopData = ActivityUtilities.GetTypedValue(turnContext.Activity)!; + feedbackLoopData.ReplyToId = turnContext.Activity.ReplyToId; + + await handler(turnContext, turnState, feedbackLoopData, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Add a handler that will execute before the turn's activity handler logic is processed. + ///
+ /// Handler returns true to continue execution of the current turn. Handler returning false + /// prevents the turn from running, but the bots state is still saved, which lets you + /// track the reason why the turn was not processed. It also means you can use this as + /// a way to call into the dialog system. For example, you could use the OAuthPrompt to sign the + /// user in before allowing the AI system to run. + ///
+ /// Function to call before turn execution. + /// The application instance for chaining purposes. + public Application OnBeforeTurn(TurnEventHandlerAsync handler) + { + Verify.ParamNotNull(handler); + _beforeTurn.Enqueue(handler); + return this; + } + + /// + /// Add a handler that will execute after the turn's activity handler logic is processed. + ///
+ /// Handler returns true to finish execution of the current turn. Handler returning false + /// prevents the bots state from being saved. + ///
+ /// Function to call after turn execution. + /// The application instance for chaining purposes. + public Application OnAfterTurn(TurnEventHandlerAsync handler) + { + Verify.ParamNotNull(handler); + _afterTurn.Enqueue(handler); + return this; + } + + /// + /// Called by the adapter (for example, a ) + /// at runtime in order to process an inbound . + /// + /// The context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + if (turnContext == null) + { + throw new ArgumentNullException(nameof(turnContext)); + } + + if (turnContext.Activity == null) + { + throw new ArgumentException($"{nameof(turnContext)} must have non-null Activity."); + } + + if (turnContext.Activity.Type == null) + { + throw new ArgumentException($"{nameof(turnContext)}.Activity must have non-null Type."); + } + + await _OnTurnAsync(turnContext, cancellationToken); + } + + /// + /// Manually start a timer to periodically send "typing" activities. + /// + /// + /// The timer waits 1000ms to send its initial "typing" activity and then send an additional + /// "typing" activity every 1000ms.The timer will automatically end once an outgoing activity + /// has been sent. If the timer is already running or the current activity is not a "message" + /// the call is ignored. + /// + /// The turn context. + public void StartTypingTimer(ITurnContext turnContext) + { + if (turnContext.Activity.Type != ActivityTypes.Message) + { + return; + } + + if (_typingTimer == null) + { + _typingTimer = new TypingTimer(_typingTimerDelay); + } + + if (!_typingTimer.IsRunning()) + { + _typingTimer.Start(turnContext); + } + + } + + /// + /// Manually stop the typing timer. + /// + /// + /// If the timer isn't running nothing happens. + /// + public void StopTypingTimer() + { + _typingTimer?.Dispose(); + _typingTimer = null; + } + + /// + /// Internal method to wrap the logic of handling a bot turn. + /// + private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + try + { + // Start typing timer if configured + if (Options.StartTypingTimer) + { + StartTypingTimer(turnContext); + }; + + // Remove @mentions + if (Options.RemoveRecipientMention && ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) + { + turnContext.Activity.Text = turnContext.Activity.RemoveRecipientMention(); + } + + // Load turn state + TState turnState = Options.TurnStateFactory!(); + IStorage? storage = Options.Storage; + + await turnState!.LoadStateAsync(storage, turnContext); + + //TODO + /* + // If user is in sign in flow, return the authentication setting name + string? settingName = AuthUtilities.UserInSignInFlow(turnState); + bool shouldStartSignIn = _startSignIn != null && await _startSignIn(turnContext, cancellationToken); + + // Sign the user in + if (this._authentication != null && (shouldStartSignIn || settingName != null)) + { + if (settingName == null) + { + settingName = this._authentication.Default; + } + + // Sets the setting name in the context object. It is used in `signIn/verifyState` & `signIn/tokenExchange` route selectors. + BotAuthenticationBase.SetSettingNameInContextActivityValue(turnContext, settingName); + + SignInResponse response = await this._authentication.SignUserInAsync(turnContext, turnState, settingName); + + if (response.Status == SignInStatus.Complete) + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + } + + if (response.Status == SignInStatus.Pending) + { + // Requires user action, save state and stop processing current activity + await turnState.SaveStateAsync(turnContext, storage); + return; + } + + if (response.Status == SignInStatus.Error && response.Cause != AuthExceptionReason.InvalidActivity) + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + throw new TeamsAIException("An error occurred when trying to sign in.", response.Error!); + } + } + */ + + // Call before turn handler + foreach (TurnEventHandlerAsync beforeTurnHandler in _beforeTurn) + { + if (!await beforeTurnHandler(turnContext, turnState, cancellationToken)) + { + // Save turn state + // - This lets the bot keep track of why it ended the previous turn. It also + // allows the dialog system to be used before the AI system is called. + await turnState!.SaveStateAsync(turnContext, storage); + + return; + } + } + + // Populate {{$temp.input}} + if ((turnState.Temp.Input == null || turnState.Temp.Input.Length == 0) && turnContext.Activity.Text != null) + { + // Use the received activity text + turnState.Temp.Input = turnContext.Activity.Text; + } + + bool eventHandlerCalled = false; + + // Run any RouteSelectors in this._invokeRoutes first if the incoming Teams activity.type is "Invoke". + // Invoke Activities from Teams need to be responded to in less than 5 seconds. + if (ActivityTypes.Invoke.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) + { + foreach (Route route in _invokeRoutes) + { + if (await route.Selector(turnContext, cancellationToken)) + { + await route.Handler(turnContext, turnState, cancellationToken); + eventHandlerCalled = true; + break; + } + } + } + + // All other ActivityTypes and any unhandled Invokes are run through the remaining routes. + if (!eventHandlerCalled) + { + foreach (Route route in _routes) + { + if (await route.Selector(turnContext, cancellationToken)) + { + await route.Handler(turnContext, turnState, cancellationToken); + eventHandlerCalled = true; + break; + } + } + } + + // Call after turn handler + foreach (TurnEventHandlerAsync afterTurnHandler in _afterTurn) + { + if (!await afterTurnHandler(turnContext, turnState, cancellationToken)) + { + return; + } + } + await turnState!.SaveStateAsync(turnContext, storage); + + } + finally + { + // Stop the timer if configured + StopTypingTimer(); + } + } + + //TODO + /* + /// + /// If the user is signed in, get the access token. If not, triggers the sign in flow for the provided authentication setting name + /// and returns.In this case, the bot should end the turn until the sign in flow is completed. + /// + /// + /// Use this method to get the access token for a user that is signed in to the bot. + /// If the user isn't signed in, this method starts the sign-in flow. + /// The bot should end the turn in this case until the sign-in flow completes and the user is signed in. + /// + /// The turn context. + /// The turn state. + /// The name of the authentication setting. + /// The cancellation token. + /// The access token for the user if they are signed, otherwise null. + /// + public async Task GetTokenOrStartSignInAsync(ITurnContext turnContext, TState turnState, string settingName, CancellationToken cancellationToken = default) + { + string? token = await Authentication.Get(settingName).IsUserSignedInAsync(turnContext, cancellationToken); + + if (token != null) + { + AuthUtilities.SetTokenInState(turnState, settingName, token); + AuthUtilities.DeleteUserInSignInFlow(turnState); + return token; + } + + // User is currently not in sign in flow + if (AuthUtilities.UserInSignInFlow(turnState) == null) + { + AuthUtilities.SetUserInSignInFlow(turnState, settingName); + } + else + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + throw new TeamsAIException("Invalid sign in flow state. Cannot start sign in when already started"); + } + + SignInResponse response = await Authentication.SignUserInAsync(turnContext, turnState, settingName); + + if (response.Status == SignInStatus.Error) + { + string message = response.Error!.ToString(); + if (response.Cause == AuthExceptionReason.InvalidActivity) + { + message = $"User is not signed in and cannot start sign in flow for this activity: {response.Error}"; + } + + throw new TeamsAIException($"Error occured while trying to authenticate user: {message}"); + } + + if (response.Status == SignInStatus.Complete) + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + return turnState.Temp.AuthTokens[settingName]; + } + + // response.Status == SignInStatus.Pending + return null; + } + */ + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs new file mode 100644 index 00000000..bbcf2a89 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs @@ -0,0 +1,125 @@ +using Microsoft.Teams.AI.State; +using Microsoft.Extensions.Logging; +using Microsoft.Agents.BotBuilder; +using System; +using Microsoft.Agents.Storage; + +namespace Microsoft.Teams.AI +{ + /// + /// A builder class for simplifying the creation of an Application instance. + /// + /// Optional. Type of the turn state. This allows for strongly typed access to the turn state. + public class ApplicationBuilder + where TState : TurnState, IMemory, new() + { + /// + /// The application's configured options. + /// + public ApplicationOptions Options { get; } = new(); + + /// + /// Configures the storage system to use for storing the bot's state. + /// + /// The storage system to use. + /// The ApplicationBuilder instance. + public ApplicationBuilder WithStorage(IStorage storage) + { + Options.Storage = storage; + return this; + } + + /// + /// Configures the turn state factory to use for managing the bot's turn state. + /// + /// The turn state factory to use. + /// The ApplicationBuilder instance. + public ApplicationBuilder WithTurnStateFactory(Func turnStateFactory) + { + Options.TurnStateFactory = turnStateFactory; + return this; + } + + /// + /// Configures the Logger factory for the application + /// + /// The Logger factory + /// The ApplicationBuilder instance. + public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + { + Options.LoggerFactory = loggerFactory; + return this; + } + + /// + /// Configures the processing of Adaptive Card requests. + /// + /// The options for Adaptive Cards. + /// The ApplicationBuilder instance. + public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveCardOptions) + { + Options.AdaptiveCards = adaptiveCardOptions; + return this; + } + + /// + /// Configures the processing of Task Module requests. + /// + /// The options for Task Modules. + /// The ApplicationBuilder instance. + public ApplicationBuilder WithTaskModuleOptions(TaskModulesOptions taskModulesOptions) + { + Options.TaskModules = taskModulesOptions; + return this; + } + + /// + /// Configures the removing of mentions of the bot's name from incoming messages. + /// Default state for removeRecipientMention is true + /// + /// The boolean for removing recipient mentions. + /// The ApplicationBuilder instance. + public ApplicationBuilder SetRemoveRecipientMention(bool removeRecipientMention) + { + Options.RemoveRecipientMention = removeRecipientMention; + return this; + } + + /// + /// Configures the typing timer when messages are received. + /// Default state for startTypingTimer is true + /// + /// The boolean for starting the typing timer. + /// The ApplicationBuilder instance. + public ApplicationBuilder SetStartTypingTimer(bool startTypingTimer) + { + Options.StartTypingTimer = startTypingTimer; + return this; + } + + //TODO + /* + /// + /// Configures authentication for the application. + /// + /// The bot adapter. + /// The options for authentication. + /// The ApplicationBuilder instance. + public ApplicationBuilder WithAuthentication(ChannelAdapter adapter, AuthenticationOptions authenticationOptions) + { + Options.Adapter = adapter; + Options.Authentication = authenticationOptions; + return this; + } + */ + + /// + /// Builds and returns a new Application instance. + /// + /// The Application instance. + public Application Build() + { + return new Application(Options); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs new file mode 100644 index 00000000..ae0d8eba --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs @@ -0,0 +1,80 @@ +using Microsoft.Teams.AI.State; +using Microsoft.Extensions.Logging; +using System; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Storage; + +namespace Microsoft.Teams.AI +{ + /// + /// Options for the class. + /// + /// Type of the turn state. + public class ApplicationOptions + where TState : TurnState, new() + { + /// + /// Optional. Teams Bot adapter being used. + /// + /// + /// If using the option, calling the method, or configuring user authentication, this property is required. + /// + public ChannelAdapter? Adapter { get; set; } + + /// + /// Optional. Application ID of the bot. + /// + /// + /// If using the option, calling the method, or configuring user authentication, this property is required. + /// + public string? BotAppId { get; set; } + + /// + /// Optional. Storage provider to use for the application. + /// + public IStorage? Storage { get; set; } + + /// + /// Optional. Options used to customize the processing of Adaptive Card requests. + /// + public AdaptiveCardsOptions? AdaptiveCards { get; set; } + + /// + /// Optional. Options used to customize the processing of Task Modules requests. + /// + public TaskModulesOptions? TaskModules { get; set; } + + /// + /// Optional. Factory used to create a custom turn state instance. + /// + public Func? TurnStateFactory { get; set; } + + /// + /// Optional. Logger factory that will be used in this application. + /// + /// + /// + public ILoggerFactory? LoggerFactory { get; set; } + + /// + /// Optional. If true, the bot will automatically remove mentions of the bot's name from incoming + /// messages. Defaults to true. + /// + public bool RemoveRecipientMention { get; set; } = true; + + /// + /// Optional. If true, the bot will automatically start a typing timer when messages are received. + /// This allows the bot to automatically indicate that it's received the message and is processing + /// the request. Defaults to true. + /// + public bool StartTypingTimer { get; set; } = true; + + //TODO + /* + /// + /// Optional. Options used to enable authentication for the application. + /// + public AuthenticationOptions? Authentication { get; set; } + */ + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs new file mode 100644 index 00000000..210a5c7b --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs @@ -0,0 +1,20 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling config events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The config data. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of ConfigResponseBase. + public delegate Task ConfigHandlerAsync(ITurnContext turnContext, TState turnState, object configData, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs new file mode 100644 index 00000000..2a2b97b5 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs @@ -0,0 +1,78 @@ +namespace Microsoft.Teams.AI +{ + /// + /// Conversation update events. + /// + public static class ConversationUpdateEvents + { + /// + /// ChannelCreated event + /// + public const string ChannelCreated = "channelCreated"; + + /// + /// ChannelRenamed event + /// + public const string ChannelRenamed = "channelRenamed"; + + /// + /// ChannelDeleted event + /// + public const string ChannelDeleted = "channelDeleted"; + + /// + /// ChannelRestored event + /// + public const string ChannelRestored = "channelRestored"; + + /// + /// MembersAdded event + /// + public const string MembersAdded = "membersAdded"; + + /// + /// MembersRemoved event + /// + public const string MembersRemoved = "membersRemoved"; + + /// + /// TeamRenamed event + /// + public const string TeamRenamed = "teamRenamed"; + + /// + /// TeamDeleted event + /// + public const string TeamDeleted = "teamDeleted"; + + /// + /// TeamArchived event + /// + public const string TeamArchived = "teamArchived"; + + /// + /// TeamUnarchived event + /// + public const string TeamUnarchived = "teamUnarchived"; + + /// + /// TeamRestored event + /// + public const string TeamRestored = "teamRestored"; + + /// + /// TeamHardDeleted event + /// + public const string TeamHardDeleted = "teamHardDeleted"; + + /// + /// TopicName event + /// + public const string TopicName = "topicName"; + + /// + /// HistoryDisclosed event + /// + public const string HistoryDisclosed = "historyDisclosed"; + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs new file mode 100644 index 00000000..ce0a2a46 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs @@ -0,0 +1,46 @@ +using System; + +namespace Microsoft.Teams.AI.Exceptions +{ + /// + /// Cause of user authentication exception. + /// + public enum AuthExceptionReason + { + /// + /// The authentication flow completed without a token. + /// + CompletionWithoutToken, + + /// + /// The incoming activity is not valid for sign in flow. + /// + InvalidActivity, + + /// + /// Other error. + /// + Other + } + + /// + /// An exception thrown when user authentication error occurs. + /// + public class AuthException : Exception + { + /// + /// The cause of the exception. + /// + public AuthExceptionReason Cause { get; } + + /// + /// Initializes the class + /// + /// The exception message + /// The cause of the exception + public AuthException(string message, AuthExceptionReason reason = AuthExceptionReason.Other) : base(message) + { + Cause = reason; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs new file mode 100644 index 00000000..f36f522b --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; + +namespace Microsoft.Teams.AI.Exceptions +{ + /// + /// A custom exception for invoke response errors. + /// + internal class InvokeResponseException : Exception + { + + /// + /// A getter for the status code + /// + public HttpStatusCode StatusCode { get; } + + /// + /// A getter for the body + /// + public object? Body { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The Http status code of the error. + /// The body of the exception. Default is null. + /// The inner exception. Default is null. + public InvokeResponseException(HttpStatusCode statusCode, object? body = null, Exception? innerException = null) : base("InvokeResponseException", innerException) + { + StatusCode = statusCode; + Body = body; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs new file mode 100644 index 00000000..1bd1a9e7 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs @@ -0,0 +1,28 @@ + +using System; + +namespace Microsoft.Teams.AI.Exceptions +{ + /// + /// Base exception for the TeamsAI library. + /// + public class TeamsAIException : Exception + { + /// + /// Create an instance of the class. + /// + /// Exception message. + public TeamsAIException(string message) : base(message) + { + } + + /// + /// Create an instance of the class. + /// + /// Exception message. + /// Inner exception. + public TeamsAIException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs new file mode 100644 index 00000000..9d477822 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs @@ -0,0 +1,40 @@ + +namespace Microsoft.Teams.AI.Application +{ + /// + /// Data returned when the thumbsup or thumbsdown button is clicked and response is received. + /// + public class FeedbackLoopData + { + /// + /// The action name. + /// + public string ActionName { get; set; } = "feedback"; + + /// + /// The action value. + /// + public FeedbackLoopDataActionValue? ActionValue { get; set; } + + /// + /// The activity ID that the feedback provided on. + /// + public string? ReplyToId { get; set; } + } + + /// + /// The feedback loop data's action value. + /// + public class FeedbackLoopDataActionValue + { + /// + /// Either "like" or "dislike" + /// + public string? Reaction { get; set; } + + /// + /// The feedback provided by the user when prompted with "What did you lke/dislike?" + /// + public string? Feedback { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs new file mode 100644 index 00000000..c7ddd9d4 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs @@ -0,0 +1,19 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.Application +{ + /// + /// Function for feedback loop activites + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The feedback loop data. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task FeedbackLoopHandler(ITurnContext turnContext, TState turnState, FeedbackLoopData feedbackLoopData, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs new file mode 100644 index 00000000..a96b7f68 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs @@ -0,0 +1,20 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling file consent card activities. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The response representing the value of the invoke activity sent when the user acts on a file consent card. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task FileConsentHandler(ITurnContext turnContext, TState turnState, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs new file mode 100644 index 00000000..5f7bfd7e --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs @@ -0,0 +1,19 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.Application +{ + /// + /// Function for handling handoff activities. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The continuation token. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task HandoffHandler(ITurnContext turnContext, TState turnState, string continuation, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs new file mode 100644 index 00000000..1fcee77d --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs @@ -0,0 +1,42 @@ + +using System; + +namespace Microsoft.Teams.AI.Application +{ + /// + /// Represents an upload file + /// + public class InputFile + { + /// + /// The downloaded content of the file + /// + public BinaryData Content { get; set; } + + /// + /// The content type of the file. + /// + public string ContentType { get; set; } + + /// + /// Optional. URL to the content of the file. + /// + public string? ContentUrl { get; set; } + + /// + /// Optional. The file name. + /// + public string? Filename { get; set; } + + /// + /// The constructor. + /// + /// The input file content + /// the input file content type + public InputFile(BinaryData content, string contentType) + { + Content = content; + ContentType = contentType; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs new file mode 100644 index 00000000..f8f399bd --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs @@ -0,0 +1,120 @@ +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Meetings class to enable fluent style registration of handlers related to Microsoft Teams Meetings. + /// + /// The type of the turn state object used by the application. + public class Meetings + where TState : TurnState, new() + { + private readonly Application _app; + + /// + /// Creates a new instance of the Meetings class. + /// + /// The top level application class to register handlers with. + public Meetings(Application app) + { + this._app = app; + } + + /// + /// Handles Microsoft Teams meeting start events. + /// + /// Function to call when a Microsoft Teams meeting start event activity is received from the connector. + /// The application instance for chaining purposes. + public Application OnStart(MeetingStartHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingStart") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + MeetingStartEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, meeting, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return _app; + } + + /// + /// Handles Microsoft Teams meeting end events. + /// + /// Function to call when a Microsoft Teams meeting end event activity is received from the connector. + /// The application instance for chaining purposes. + public Application OnEnd(MeetingEndHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingEnd") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + MeetingEndEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, meeting, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return _app; + } + + /// + /// Handles Microsoft Teams meeting participants join events. + /// + /// Function to call when a Microsoft Teams meeting participants join event activity is received from the connector. + /// The application instance for chaining purposes. + public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingParticipantJoin") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + MeetingParticipantsEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, meeting, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return _app; + } + + /// + /// Handles Microsoft Teams meeting participants leave events. + /// + /// Function to call when a Microsoft Teams meeting participants leave event activity is received from the connector. + /// The application instance for chaining purposes. + public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingParticipantLeave") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + MeetingParticipantsEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + await handler(turnContext, turnState, meeting, cancellationToken); + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return _app; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs new file mode 100644 index 00000000..3f5c2f5c --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs @@ -0,0 +1,45 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling Microsoft Teams meeting start events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The details of the meeting. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task MeetingStartHandler(ITurnContext turnContext, TState turnState, MeetingStartEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Microsoft Teams meeting end events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The details of the meeting. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task MeetingEndHandler(ITurnContext turnContext, TState turnState, MeetingEndEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Microsoft Teams meeting participants join or leave events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The details of the meeting. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task MeetingParticipantsEventHandler(ITurnContext turnContext, TState turnState, MeetingParticipantsEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs new file mode 100644 index 00000000..c2302b1f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs @@ -0,0 +1,793 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Constants for message extension invoke names + /// + public class MessageExtensionsInvokeNames + { + /// + /// Fetch task invoke name + /// + public static readonly string FETCH_TASK_INVOKE_NAME = "composeExtension/fetchTask"; + /// + /// Query invoke name + /// + public static readonly string QUERY_INVOKE_NAME = "composeExtension/query"; + /// + /// Query link invoke name + /// + public static readonly string QUERY_LINK_INVOKE_NAME = "composeExtension/queryLink"; + /// + /// Anonymous query link invoke name + /// + public static readonly string ANONYMOUS_QUERY_LINK_INVOKE_NAME = "composeExtension/anonymousQueryLink"; + } + + /// + /// MessageExtensions class to enable fluent style registration of handlers related to Message Extensions. + /// + /// The type of the turn state object used by the application. + public class MessageExtensions + where TState : TurnState, new() + { + private static readonly string SUBMIT_ACTION_INVOKE_NAME = "composeExtension/submitAction"; + private static readonly string SELECT_ITEM_INVOKE_NAME = "composeExtension/selectItem"; + private static readonly string CONFIGURE_SETTINGS = "composeExtension/setting"; + private static readonly string QUERY_SETTING_URL = "composeExtension/querySettingUrl"; + private static readonly string QUERY_CARD_BUTTON_CLICKED = "composeExtension/onCardButtonClicked"; + + private readonly Application _app; + + /// + /// Creates a new instance of the MessageExtensions class. + /// + /// The top level application class to register handlers with. + public MessageExtensions(Application app) + { + this._app = app; + } + + /// + /// Registers a handler that implements the submit action for an Action based Message Extension. + /// + /// ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) + { + Verify.ParamNotNull(commandId); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME); + return OnSubmitAction(routeSelector, handler); + } + + /// + /// Registers a handler that implements the submit action for an Action based Message Extension. + /// + /// Regular expression to match against the ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) + { + Verify.ParamNotNull(commandIdPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME); + return OnSubmitAction(routeSelector, handler); + } + + /// + /// Registers a handler that implements the submit action for an Action based Message Extension. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmitAction(RouteSelectorAsync routeSelector, SubmitActionHandlerAsync handler) + { + MessagingExtensionAction? messagingExtensionAction; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) + || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + { + throw new TeamsAIException($"Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: {turnContext.Activity.Type}"); + } + + MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.Data, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements the submit action for an Action based Message Extension. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string commandId in routeSelectors.Strings) + { + OnSubmitAction(commandId, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex commandIdPattern in routeSelectors.Regexes) + { + OnSubmitAction(commandIdPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnSubmitAction(routeSelector, handler); + } + } + return _app; + } + + /// + /// Registers a handler to process the 'edit' action of a message that's being previewed by the + /// user prior to sending. + /// + /// ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) + { + Verify.ParamNotNull(commandId); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME, "edit"); + return OnBotMessagePreviewEdit(routeSelector, handler); + } + + /// + /// Registers a handler to process the 'edit' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Regular expression to match against the ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) + { + Verify.ParamNotNull(commandIdPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME, "edit"); + return OnBotMessagePreviewEdit(routeSelector, handler); + } + + /// + /// Registers a handler to process the 'edit' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, BotMessagePreviewEditHandlerAsync handler) + { + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessagingExtensionAction? messagingExtensionAction; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) + || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "edit")) + { + throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: {turnContext.Activity.Type}"); + } + + MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.BotActivityPreview[0], cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler to process the 'edit' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string commandId in routeSelectors.Strings) + { + OnBotMessagePreviewEdit(commandId, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex commandIdPattern in routeSelectors.Regexes) + { + OnBotMessagePreviewEdit(commandIdPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnBotMessagePreviewEdit(routeSelector, handler); + } + } + return _app; + } + + /// + /// Registers a handler to process the 'send' action of a message that's being previewed by the + /// user prior to sending. + /// + /// ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) + { + Verify.ParamNotNull(commandId); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME, "send"); + return OnBotMessagePreviewSend(routeSelector, handler); + } + + /// + /// Registers a handler to process the 'send' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Regular expression to match against the ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) + { + Verify.ParamNotNull(commandIdPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME, "send"); + return OnBotMessagePreviewSend(routeSelector, handler); + } + + /// + /// Registers a handler to process the 'send' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessagingExtensionAction? messagingExtensionAction; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) + || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "send")) + { + throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: {turnContext.Activity.Type}"); + } + + Activity activityPreview = messagingExtensionAction.BotActivityPreview.Count > 0 ? messagingExtensionAction.BotActivityPreview[0] : new Activity(); + await handler(turnContext, turnState, activityPreview, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new(); + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler to process the 'send' action of a message that's being previewed by the + /// user prior to sending. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string commandId in routeSelectors.Strings) + { + OnBotMessagePreviewSend(commandId, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex commandIdPattern in routeSelectors.Regexes) + { + OnBotMessagePreviewSend(commandIdPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnBotMessagePreviewSend(routeSelector, handler); + } + } + return _app; + } + + /// + /// Registers a handler to process the initial fetch task for an Action based message extension. + /// + /// ID of the commands to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) + { + Verify.ParamNotNull(commandId); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME); + return OnFetchTask(routeSelector, handler); + } + + /// + /// Registers a handler to process the initial fetch task for an Action based message extension. + /// + /// Regular expression to match against the ID of the commands to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) + { + Verify.ParamNotNull(commandIdPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME); + return OnFetchTask(routeSelector, handler); + } + + /// + /// Registers a handler to process the initial fetch task for an Action based message extension. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME)) + { + throw new TeamsAIException($"Unexpected MessageExtensions.OnFetchTask() triggered for activity type: {turnContext.Activity.Type}"); + } + + TaskModuleResponse result = await handler(turnContext, turnState, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler to process the initial fetch task for an Action based message extension. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string commandId in routeSelectors.Strings) + { + OnFetchTask(commandId, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex commandIdPattern in routeSelectors.Regexes) + { + OnFetchTask(commandIdPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnFetchTask(routeSelector, handler); + } + } + return _app; + } + + /// + /// Registers a handler that implements a Search based Message Extension. + /// + /// ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnQuery(string commandId, QueryHandlerAsync handler) + { + Verify.ParamNotNull(commandId); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), MessageExtensionsInvokeNames.QUERY_INVOKE_NAME); + return OnQuery(routeSelector, handler); + } + + /// + /// Registers a handler that implements a Search based Message Extension. + /// + /// Regular expression to match against the ID of the command to register the handler for. + /// Function to call when the command is received. + /// The application instance for chaining purposes. + public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) + { + Verify.ParamNotNull(commandIdPattern); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), MessageExtensionsInvokeNames.QUERY_INVOKE_NAME); + return OnQuery(routeSelector, handler); + } + + /// + /// Registers a handler that implements a Search based Message Extension. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessagingExtensionQuery? messagingExtensionQuery; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.QUERY_INVOKE_NAME) + || (messagingExtensionQuery = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + { + throw new TeamsAIException($"Unexpected MessageExtensions.OnQuery() triggered for activity type: {turnContext.Activity.Type}"); + } + + IDictionary parameters = new Dictionary(); + foreach (MessagingExtensionParameter parameter in messagingExtensionQuery.Parameters) + { + parameters.Add(parameter.Name, parameter.Value); + } + Query> query = new(messagingExtensionQuery.QueryOptions.Count ?? 25, messagingExtensionQuery.QueryOptions.Skip ?? 0, parameters); + MessagingExtensionResult result = await handler(turnContext, turnState, query, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new() + { + ComposeExtension = result + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements a Search based Message Extension. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + if (routeSelectors.Strings != null) + { + foreach (string commandId in routeSelectors.Strings) + { + OnQuery(commandId, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex commandIdPattern in routeSelectors.Regexes) + { + OnQuery(commandIdPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnQuery(routeSelector, handler); + } + } + return _app; + } + + /// + /// Registers a handler that implements the logic to handle the tap actions for items returned + /// by a Search based message extension. + /// + /// The `composeExtension/selectItem` INVOKE activity does not contain any sort of command ID, + /// so only a single select item handler can be registered. Developers will need to include a + /// type name of some sort in the preview item they return if they need to support multiple + /// select item handlers. + /// > + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnSelectItem(SelectItemHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, SELECT_ITEM_INVOKE_NAME)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessagingExtensionResult result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new() + { + ComposeExtension = result + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements a Link Unfurling based Message Extension. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnQueryLink(QueryLinkHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.QUERY_LINK_INVOKE_NAME)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + AppBasedLinkQuery? appBasedLinkQuery = ActivityUtilities.GetTypedValue(turnContext.Activity); + MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new() + { + ComposeExtension = result + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements the logic to handle anonymous link unfurling. + /// + /// + /// The `composeExtension/anonymousQueryLink` INVOKE activity does not contain any sort of command ID, + /// so only a single select item handler can be registered. + /// For more information visit https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?#enable-zero-install-link-unfurling + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.ANONYMOUS_QUERY_LINK_INVOKE_NAME)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + AppBasedLinkQuery? appBasedLinkQuery = ActivityUtilities.GetTypedValue(turnContext.Activity); + MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new() + { + ComposeExtension = result + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that invokes the fetch of the configuration settings for a Message Extension. + /// + /// + /// The `composeExtension/querySettingUrl` INVOKE activity does not contain a command ID, so only a single select item handler can be registered. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, QUERY_SETTING_URL)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + MessagingExtensionResult result = await handler(turnContext, turnState, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + MessagingExtensionActionResponse response = new() + { + ComposeExtension = result + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements the logic to invoke configuring Message Extension settings. + /// + /// + /// The `composeExtension/setting` INVOKE activity does not contain a command ID, so only a single select item handler can be registered. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnConfigureSettings(ConfigureSettingsHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, CONFIGURE_SETTINGS)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler that implements the logic when a user has clicked on a button in a Message Extension card. + /// + /// + /// The `composeExtension/onCardButtonClicked` INVOKE activity does not contain any sort of command ID, + /// so only a single select item handler can be registered. Developers will need to include a + /// type name of some sort in the preview item they return if they need to support multiple select item handlers. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnCardButtonClicked(CardButtonClickedHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, QUERY_CARD_BUTTON_CLICKED)); + }; + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + private static RouteSelectorAsync CreateTaskSelector(Func isMatch, string invokeName, string? botMessagePreviewAction = default) + { + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + bool isInvoke = string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, invokeName); + if (!isInvoke) + { + return Task.FromResult(false); + } + + // TODO + /* + JObject? obj = turnContext.Activity.Value as JObject; + if (obj == null) + { + return Task.FromResult(false); + } + bool isCommandMatch = obj.TryGetValue("commandId", out JToken? commandId) && commandId != null && commandId.Type == JTokenType.String && isMatch(commandId.Value()!); + JToken? previewActionToken = obj.GetValue("botMessagePreviewAction"); + bool isPreviewActionMatch = string.IsNullOrEmpty(botMessagePreviewAction) + ? previewActionToken == null || string.IsNullOrEmpty(previewActionToken.Value()) + : previewActionToken != null && string.Equals(botMessagePreviewAction, previewActionToken.Value()); + return Task.FromResult(isCommandMatch && isPreviewActionMatch); + */ + return Task.FromResult(false); + }; + return routeSelector; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs new file mode 100644 index 00000000..8d9de6d9 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs @@ -0,0 +1,128 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling Message Extension submitAction events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data that was submitted. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionActionResponse. + public delegate Task SubmitActionHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension botMessagePreview edit events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The activity that's being previewed by the user. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionActionResponse. + public delegate Task BotMessagePreviewEditHandlerAsync(ITurnContext turnContext, TState turnState, IActivity activityPreview, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension botMessagePreview send events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The activity that's being previewed by the user. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task BotMessagePreviewSendHandler(ITurnContext turnContext, TState turnState, IActivity activityPreview, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension fetchTask events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of TaskModuleResponse. + public delegate Task FetchTaskHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension query events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The query parameters that were sent by the client. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionResult. + public delegate Task QueryHandlerAsync(ITurnContext turnContext, TState turnState, Query> query, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension selecting item events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The item that was selected. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionResult. + public delegate Task SelectItemHandlerAsync(ITurnContext turnContext, TState turnState, object item, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension link unfurling events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The URL that should be unfurled. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionResult. + public delegate Task QueryLinkHandlerAsync(ITurnContext turnContext, TState turnState, string url, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension configuring query setting url events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// An instance of MessagingExtensionResult. + public delegate Task QueryUrlSettingHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension configuring settings events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The configuration settings that was submitted. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task ConfigureSettingsHandler(ITurnContext turnContext, TState turnState, object settings, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Message Extension clicking card button events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The card data. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task CardButtonClickedHandler(ITurnContext turnContext, TState turnState, object cardData, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs new file mode 100644 index 00000000..8e43a564 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs @@ -0,0 +1,20 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling O365 Connector Card Action activities. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The O365 connector card HttpPOST invoke query. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public delegate Task O365ConnectorCardActionHandler(ITurnContext turnContext, TState turnState, O365ConnectorCardActionQuery query, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs new file mode 100644 index 00000000..cd41f03b --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs @@ -0,0 +1,37 @@ +namespace Microsoft.Teams.AI +{ + /// + /// Query arguments for Message Extension query and Adaptive Card dynamic search. + /// + /// Type of the query parameters. + public class Query + { + /// + /// Number of items to return in the result set. + /// + public int Count { get; set; } + + /// + /// Number of items to skip in the result set. + /// + public int Skip { get; set; } + + /// + /// Query parameters. + /// + public TParams Parameters { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Number of items to return in the result set. + /// Number of items to skip in the result set. + /// Query parameters. + public Query(int count, int skip, TParams parameters) + { + this.Count = count; + this.Skip = skip; + this.Parameters = parameters; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs new file mode 100644 index 00000000..ee3e807c --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs @@ -0,0 +1,19 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling read receipt events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The read receipt data. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + public delegate Task ReadReceiptHandler(ITurnContext turnContext, TState turnState, ReadReceiptInfo data, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs new file mode 100644 index 00000000..503e3591 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs @@ -0,0 +1,25 @@ +using System.Text.RegularExpressions; + +namespace Microsoft.Teams.AI +{ + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// + public class MultipleRouteSelector + { + /// + /// The string selectors. + /// + public string[]? Strings { get; set; } + + /// + /// The Regex selectors. + /// + public Regex[]? Regexes { get; set; } + + /// + /// The RouteSelectorAsync function selectors. + /// + public RouteSelectorAsync[]? RouteSelectors { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs new file mode 100644 index 00000000..b686fc17 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs @@ -0,0 +1,57 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for selecting whether a route handler should be triggered. + /// + /// Context for the current turn of conversation with the user. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// True if the route handler should be triggered. Otherwise, False. + public delegate Task RouteSelectorAsync(ITurnContext turnContext, CancellationToken cancellationToken); + + /// + /// The common route handler. Function for handling an incoming request. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// + public delegate Task RouteHandler(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + + internal class Route where TState : TurnState + { + public Route(RouteSelectorAsync selector, bool isInvokeRoute = false) + { + Selector = selector; + Handler = (_, _, _) => Task.CompletedTask; + IsInvokeRoute = isInvokeRoute; + } + + public Route(RouteHandler handler, bool isInvokeRoute = false) + { + Selector = (_, _) => Task.FromResult(true); + Handler = handler; + IsInvokeRoute = isInvokeRoute; + } + + public Route(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) + { + Selector = selector; + Handler = handler; + IsInvokeRoute = isInvokeRoute; + } + + public RouteSelectorAsync Selector { get; private set; } + + public RouteHandler Handler { get; private set; } + + public bool IsInvokeRoute { get; private set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs new file mode 100644 index 00000000..f2bfd3b9 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs @@ -0,0 +1,39 @@ +namespace Microsoft.Teams.AI.State +{ + /// + /// Represents a memory, a key-value store that can be used to store and retrieve values. + /// + public interface IMemory + { + /// + /// Deletes a value from the memory. + /// + /// Path to the value to delete in the form of `[scope].property`. + /// If scope is omitted, the value is deleted from the temporary scope. + void DeleteValue(string path); + + /// + /// Checks if a value exists in the memory. + /// + /// Path to the value to check in the form of `[scope].property`. + /// If scope is omitted, the value is checked in the temporary scope. + /// True if the value exists, false otherwise. + bool HasValue(string path); + + /// + /// Retrieves a value from the memory. + /// + /// Path to the value to retrieve in the form of `[scope].property`. + /// If scope is omitted, the value is retrieved from the temporary scope. + /// The value or undefined if not found. + object? GetValue(string path); + + /// + /// Assigns a value to the memory. + /// + /// Path to the value to assign in the form of `[scope].property`. + /// If scope is omitted, the value is assigned to the temporary scope. + /// Value to assign. + void SetValue(string path, object value); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs new file mode 100644 index 00000000..cec545fa --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs @@ -0,0 +1,51 @@ + +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Storage; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.State +{ + /// + /// The turn state interface. + /// + /// The conversation state class. + /// The user state class. + /// The temp state class. + public interface ITurnState + where TConversationState : class + where TUserState : class + where TTempState : TempState + { + /// + /// Gets the conversation state. + /// + public TConversationState? Conversation { get; } + + /// + /// Gets the user state. + /// + public TUserState? User { get; } + + /// + /// Gets the temp state. + /// + public TTempState? Temp { get; } + + /// + /// Loads all of the state scopes for the current turn. + /// + /// Optional. Storage provider to load state scopes from. + /// Context for the current turn of conversation with the user. + /// + /// True if the states need to be loaded. + public Task LoadStateAsync(IStorage? storage, ITurnContext turnContext, CancellationToken cancellationToken = default); + + /// + /// Saves all of the state scopes for the current turn. + /// + /// Optional. Storage provider to save state scopes to. + /// Context for the current turn of conversation with the user. + public Task SaveStateAsync(IStorage? storage, ITurnContext turnContext); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs new file mode 100644 index 00000000..9db86d35 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Teams.AI.State +{ + + /// + /// Forks an existing memory. + /// A memory fork is a memory that is a copy of another memory, but can be modified without affecting the original memory. + /// + public class MemoryFork : IMemory + { + private readonly Dictionary _fork = new(); + private readonly IMemory? _memory; + + /// + /// Creates a new `MemoryFork` instance. + /// + /// Memory to fork. + public MemoryFork(IMemory? memory = null) + { + _memory = memory; + } + + /// + /// Deletes a value from the memory. Only forked values will be deleted. + /// + /// Path to the value to delete in the form of `[scope].property`. + /// If scope is omitted, the value is deleted from the temporary scope. + public void DeleteValue(string path) + { + (string scope, string name) = GetScopeAndName(path); + if (_fork.ContainsKey(scope) && _fork[scope].ContainsKey(name)) + { + _fork[scope].Remove(name); + } + } + + /// + /// Retrieves a value from the memory. The forked memory is checked first, then the original memory. + /// + /// Path to the value to retrieve in the form of `[scope].property`. + /// If scope is omitted, the value is retrieved from the temporary scope. + /// The value or undefined if not found. + public object? GetValue(string path) + { + (string scope, string name) = GetScopeAndName(path); + if (_fork.ContainsKey(scope)) + { + if (_fork[scope].ContainsKey(name)) + { + return _fork[scope][name]; + } + } + + return _memory?.GetValue(path); + } + + /// + /// Checks if a value exists in the memory. The forked memory is checked first, then the original memory. + /// + /// Path to the value to check in the form of `[scope].property`. + /// If scope is omitted, the value is checked in the temporary scope. + /// True if the value exists, false otherwise. + public bool HasValue(string path) + { + (string scope, string name) = GetScopeAndName(path); + if (_fork.ContainsKey(scope)) + { + return _fork[scope].ContainsKey(name); + } + + if (_memory != null) + { + return _memory.HasValue(path); + } + + return false; + } + + /// + /// Assigns a value to the memory. The value is assigned to the forked memory. + /// + /// Path to the value to assign in the form of `[scope].property`. + /// If scope is omitted, the value is assigned to the temporary scope. + /// Value to assign. + public void SetValue(string path, object value) + { + (string scope, string name) = GetScopeAndName(path); + if (!_fork.ContainsKey(scope)) + { + _fork[scope] = new(); + } + + _fork[scope][name] = value; + } + + private (string, string) GetScopeAndName(string path) + { + List parts = path.Split('.').ToList(); + + if (parts.Count > 2) + { + throw new InvalidOperationException($"Invalid state path: {path}"); + } + if (parts.Count == 1) + { + parts.Insert(0, "temp"); + } + return (parts[0], parts[1]); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs new file mode 100644 index 00000000..16972328 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs @@ -0,0 +1,81 @@ +using Microsoft.Agents.Core.Serialization; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Generic; + +namespace Microsoft.Teams.AI.State +{ + /// + /// The class representing a record. + /// + public class Record : Dictionary + { + /// + /// Tries to get the value from the dictionary. + /// + /// Type of the value + /// key to look for + /// value associated with key + /// True if a value of given type is associated with key. + /// + public bool TryGetValue(string key, out T value) + { + Verify.ParamNotNull(key); + + if (base.TryGetValue(key, out object entry)) + { + if (entry is T castedEntry) + { + value = castedEntry; + return true; + }; + + //throw new InvalidCastException($"Failed to cast generic object to type '{typeof(T)}'"); + value = ProtocolJsonSerializer.ToObject(entry); + return true; + } + +#pragma warning disable CS8601 // Possible null reference assignment. + value = default; +#pragma warning restore CS8601 // Possible null reference assignment. + + return false; + } + + /// + /// Gets the value from the dictionary. + /// + /// Type of the value + /// key to look for + /// The value associated with the key + public T? Get(string key) + { + Verify.ParamNotNull(key); + + if (TryGetValue(key, out T value)) + { + return value; + } + else + { + return default; + }; + } + + /// + /// Sets value in the dictionary. + /// + /// Type of value + /// key to look for + /// value associated with key + public void Set(string key, T value) + { + Verify.ParamNotNull(key); + Verify.ParamNotNull(value); + +#pragma warning disable CS8601 // Possible null reference assignment. + this[key] = value; +#pragma warning restore CS8601 // Possible null reference assignment. + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs new file mode 100644 index 00000000..442d18b0 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs @@ -0,0 +1,113 @@ + +using Microsoft.Teams.AI.Application; +using System.Collections.Generic; + +namespace Microsoft.Teams.AI.State +{ + /// + /// Temporary state. + /// + /// + /// Inherit a new class from this base abstract class to strongly type the applications temp state. + /// + public class TempState : Record + { + /// + /// Name of the input property. + /// + public const string InputKey = "input"; + + /// + /// Name of the output property. + /// + public const string OutputKey = "output"; + + /// + /// Name of the action outputs property. + /// + public const string ActionOutputsKey = "actionOutputs"; + + /// + /// Name of the auth tokens property. + /// + public const string AuthTokenKey = "authTokens"; + + /// + /// Name of the duplicate token exchange property + /// + public const string DuplicateTokenExchangeKey = "duplicateTokenExchange"; + + /// + /// Name of the input files key + /// + public const string InputFilesKey = "inputFiles"; + + /// + /// Creates a new instance of the class. + /// + public TempState() : base() + { + this[InputKey] = string.Empty; + this[OutputKey] = string.Empty; + this[ActionOutputsKey] = new Dictionary(); + this[AuthTokenKey] = new Dictionary(); + this[DuplicateTokenExchangeKey] = false; + this[InputFilesKey] = new List(); + } + + /// + /// Input passed to an AI prompt + /// + public string Input + { + get => Get(InputKey)!; + set => Set(InputKey, value); + } + + // TODO: This is currently not used, should store AI prompt/function output here + /// + /// Output returned from an AI prompt or function + /// + public string Output + { + get => Get(OutputKey)!; + set => Set(OutputKey, value); + } + + /// + /// All outputs returned from the action sequence that was executed. + /// + public Dictionary ActionOutputs + { + get => Get>(ActionOutputsKey)!; + set => Set(ActionOutputsKey, value); + } + + /// + /// All tokens acquired after sign-in for current activity + /// + public Dictionary AuthTokens + { + get => Get>(AuthTokenKey)!; + set => Set(AuthTokenKey, value); + } + + /// + /// Whether current token exchange is a duplicate one + /// + public bool DuplicateTokenExchange + { + get => Get(DuplicateTokenExchangeKey)!; + set => Set(DuplicateTokenExchangeKey, value); + } + + /// + /// Downloaded files passed by the user to the AI library + /// + public List InputFiles + { + get => Get>(InputFilesKey)!; + set => Set(InputFilesKey, value); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs new file mode 100644 index 00000000..87c717a8 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs @@ -0,0 +1,474 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Storage; +using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.State +{ + /// + /// Base class defining a collection of turn state scopes. + /// Developers can create a derived class that extends `TurnState` to add additional state scopes. + /// + public class TurnState : IMemory + { + private Dictionary _scopes = new(); + private bool _isLoaded = false; + private Task? _loadingTask = Task.FromResult(false); + + /// + /// Name of the conversation scope. + /// + public const string CONVERSATION_SCOPE = "conversation"; + + /// + /// Name of the user scope. + /// + public const string USER_SCOPE = "user"; + + /// + /// Name of the temp scope. + /// + public const string TEMP_SCOPE = "temp"; + + /// + /// Initializes a new instance of the class. + /// + public TurnState() + { + ScopeDefaults = new Dictionary(); + ScopeDefaults.Add(CONVERSATION_SCOPE, new Record()); + ScopeDefaults.Add(USER_SCOPE, new Record()); + } + + /// + /// The default values to initial for each scope. + /// + protected Dictionary ScopeDefaults; + + /// + /// Provides the current status of the load. + /// + /// Returns true if the scopes have been loaded, false otherwise. + public bool IsLoaded() { return _isLoaded; } + + /// + /// Returns the TurnStateEntry associated with the specified scope. + /// + /// The specified scope. + /// The state saved for the scope. + public TurnStateEntry? GetScope(string name) + { + try + { + return _scopes[name]; + } + catch (KeyNotFoundException) + { + return null; + } + } + + /// + /// Stores all the conversation-related state. + /// + public Record Conversation + { + get + { + TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + return scope.Value!; + } + set + { + Verify.ParamNotNull(value); + + TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + scope.Replace(value!); + } + } + + /// + /// Stores all the user related state. + /// + public Record User + { + get + { + TurnStateEntry? scope = GetScope(USER_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + return scope.Value!; + } + set + { + Verify.ParamNotNull(value); + + TurnStateEntry? scope = GetScope(USER_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + scope.Replace(value!); + } + } + + /// + /// Stores all the temporary state for the current turn. + /// + public TempState Temp + { + get + { + TurnStateEntry? scope = GetScope(TEMP_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + return (TempState)scope.Value!; + } + set + { + Verify.ParamNotNull(value); + + TurnStateEntry? scope = GetScope(TEMP_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + scope.Replace(value!); + } + } + + /// + /// Deletes the conversation state. + /// + public void DeleteConversationState() + { + TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + scope.Delete(); + } + + /// + /// Deletes the temporary state. + /// + public void DeleteTempState() + { + TurnStateEntry? scope = GetScope(TEMP_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + scope.Delete(); + } + + /// + /// Deletes the user state + /// + public void DeleteUserState() + { + TurnStateEntry? scope = GetScope(USER_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + scope.Delete(); + } + + /// + /// Deletes a value from the memory. + /// + /// Path to the value to delete in the form of `[scope].property`. + /// If scope is omitted, the value is deleted from the temporary scope. + public void DeleteValue(string path) + { + (TurnStateEntry scope, string name) = GetScopeAndName(path); + if (scope.Value!.ContainsKey(name)) + { + scope.Value.Remove(name); + } + } + + /// + /// Checks if a value exists in the memory. + /// + /// Path to the value to check in the form of `[scope].property`. + /// If scope is omitted, the value is checked in the temporary scope. + /// True if the value exists, false otherwise. + public bool HasValue(string path) + { + (TurnStateEntry scope, string name) = GetScopeAndName(path); + return scope.Value?.ContainsKey(name) == true; + } + + /// + /// Retrieves a value from the memory. + /// + /// Path to the value to retrieve in the form of `[scope].property`. + /// If scope is omitted, the value is retrieved from the temporary scope. + /// The value or undefined if not found. + public object? GetValue(string path) + { + (TurnStateEntry scope, string name) = GetScopeAndName(path); + + if (scope.Value?.ContainsKey(name) != true) + { + return null; + } + + return scope.Value[name]; + } + + /// + /// Assigns a value to the memory. + /// + /// Path to the value to assign in the form of `[scope].property`. + /// If scope is omitted, the value is assigned to the temporary scope. + /// Value to assign. + public void SetValue(string path, object value) + { + (TurnStateEntry scope, string name) = GetScopeAndName(path); + scope.Value![name] = value; + } + + /// + /// Loads all of the state scopes for the current turn. + /// + /// Optional. Storage provider to load state scopes from. + /// Context for the current turn of conversation with the user. + /// Optional. The cancellation token. + /// True if the states need to be loaded. + public async Task LoadStateAsync(IStorage? storage, ITurnContext turnContext, CancellationToken cancellationToken = default) + { + // Only load on first call + if (this._isLoaded) + { + return false; + } + + // Check for existing load operation + if (!(await this._loadingTask!)) + { + this._loadingTask = Task.Run(async () => + { + try + { + // Prevent additional load attempts + this._isLoaded = true; + + // Compute state keys + List keys = new(); + Dictionary scopes = OnComputeStorageKeys(turnContext); + foreach (KeyValuePair scope in scopes) + { + if (scopes.ContainsKey(scope.Key)) + { + keys.Add(scopes[scope.Key]); + } + } + + // Read items from storage provider (if configured) + IDictionary items; + if (storage != null) + { + items = await storage.ReadAsync(keys.Where(k => k != TEMP_SCOPE).ToArray(), cancellationToken); + } + else + { + items = new Dictionary(); + } + + // Create scopes for items + foreach (KeyValuePair scope in scopes) + { + if (scopes.ContainsKey(scope.Key)) + { + Record scopeDefault = ScopeDefaults.ContainsKey(scope.Key) ? ScopeDefaults[scope.Key] : new Record(); + string storageKey = scopes[scope.Key]; + object value = items.ContainsKey(storageKey) ? items[storageKey] : scopeDefault; + this._scopes[scope.Key] = new TurnStateEntry((value as Record)!, storageKey); + } + } + + // Add the temp scope + Record tempStateValue = ScopeDefaults.ContainsKey(TEMP_SCOPE) ? ScopeDefaults[TEMP_SCOPE] : new TempState(); + this._scopes[TEMP_SCOPE] = new TurnStateEntry(tempStateValue); + + // Clear loading task + this._isLoaded = true; + this._loadingTask = null; + return true; + } + catch (Exception ex) + { + this._loadingTask = null; + throw new TeamsAIException($"Something went wrong when loading state: {ex.Message}", ex); + } + }); + } + + return this._loadingTask.Result; + } + + /// + /// Saves all of the state scopes for the current turn. + /// + /// Context for the current turn of conversation with the user. + /// Optional. Storage provider to save state scopes to. + public async Task SaveStateAsync(ITurnContext turnContext, IStorage? storage) + { + Verify.ParamNotNull(turnContext); + + // Check for existing load operation + if (!this._isLoaded && this._loadingTask!.Result) + { + // Wait for load to finish + await this._loadingTask; + } + + // Ensure loaded + if (!this._isLoaded) + { + throw new ArgumentNullException($"TurnState hasn't been loaded. Call loadState() first."); + } + + // Find changes and deletions + Record changes = new(); + List deletions = new(); + + foreach (KeyValuePair scope in this._scopes) + { + if (!this._scopes.ContainsKey(scope.Key) || scope.Key == TEMP_SCOPE) + { + continue; + } + TurnStateEntry entry = this._scopes[scope.Key]; + if (entry.StorageKey != null) + { + if (entry.IsDeleted) + { + // Add to deletion list + if (deletions != null) + { + deletions.Add(entry.StorageKey); + } + else + { + deletions = new() { entry.StorageKey }; + } + } + else if (entry.HasChanged) + { + // Add to change set + if (changes == null) + { + changes = new(); + } + changes[entry.StorageKey] = entry.Value!; + } + } + } + + // Do we have a storage provider? + if (storage != null) + { + // Apply changes + List tasks = new(); + if (changes.Keys.Count > 0) + { + tasks.Add(storage.WriteAsync(changes)); + } + + // Apply deletions + if (deletions.Count > 0) + { + tasks.Add(storage.DeleteAsync(deletions.ToArray())); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks.ToArray()); + } + } + } + + /// + /// Computes the state keys + /// + /// Context for the current turn. + /// Stored conversation and user scopes. + protected virtual Dictionary OnComputeStorageKeys(ITurnContext context) + { + // Compute state keys + var activity = context.Activity; + string channelId = activity.ChannelId; + string botId = activity.Recipient.Id; + string conversationId = activity.Conversation.Id; + string userId = activity.From.Id; + + Verify.ParamNotNull(activity, "TurnContext.Activity"); + Verify.ParamNotNull(channelId, "TurnContext.Activity.ChannelId"); + Verify.ParamNotNull(botId, "TurnContext.Activity.Recipient.Id"); + Verify.ParamNotNull(conversationId, "TurnContext.Activity.Conversation.Id"); + Verify.ParamNotNull(userId, "TurnContext.Activity.From.Id"); + + string conversationKey = $"{channelId}/${botId}/conversations/${conversationId}"; + string userKey = $"{channelId}/${botId}/users/${userId}"; + + Dictionary keys = new(); + keys.Add(CONVERSATION_SCOPE, conversationKey); + keys.Add(USER_SCOPE, userKey); + keys.Add(TEMP_SCOPE, TEMP_SCOPE); + return keys; + } + + private (TurnStateEntry, string) GetScopeAndName(string path) + { + // Get variable scope and name + string[] parts = path.Split('.'); + if (parts.Length > 2) + { + throw new ArgumentException($"Invalid state path: {path}"); + } + else if (parts.Length == 1) + { + parts = parts.Prepend(TEMP_SCOPE).ToArray(); + } + + // Validate scope + TurnStateEntry? scope = GetScope(parts[0]); + if (scope == null) + { + throw new ArgumentNullException($"Invalid state scope: {parts[0]}"); + } + + return (scope, parts[1]); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs new file mode 100644 index 00000000..7d980fe8 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs @@ -0,0 +1,87 @@ +using Microsoft.Teams.AI.Utilities; +using System.Runtime.CompilerServices; +using System.Text.Json; + +namespace Microsoft.Teams.AI.State +{ + /// + /// Accessor class for managing an individual state scope. + /// + public class TurnStateEntry + { + private Record _value; + private string _hash; + private static readonly JsonSerializerOptions _serializerOptions = new() { MaxDepth = 64 }; + + /// + /// Constructs the turn state entry. + /// + /// Value to initialize the state scope with. The default is an {} object. + /// Storage key to use when persisting the state scope. + public TurnStateEntry(Record value, string? storageKey = null) + { + Verify.ParamNotNull(value); + _value = value; + StorageKey = storageKey; + _hash = ComputeHash(value); + } + + /// + public bool HasChanged + { + get { return ComputeHash(_value!) != _hash; } + } + + /// + public bool IsDeleted { get; private set; } = false; + + /// + public Record? Value + { + get + { + if (IsDeleted) + { + _value = new(); + IsDeleted = false; + } + + return _value; + } + } + + /// + public string? StorageKey { get; } + + /// + /// Clears the state scope. + /// + public void Delete() + { + IsDeleted = true; + } + + /// + /// Replaces the state scope with a new value. + /// + /// New value to replace the state scope with. + public void Replace(Record value) + { + Verify.ParamNotNull(value); + _value = value; + } + + // TODO: Optimize if possible + /// + /// Computes the hash from the object + /// + /// The object to compute has from + /// Returns a Json object representation + internal static string ComputeHash(object obj) + { + Verify.ParamNotNull(obj); + + return JsonSerializer.Serialize(obj, _serializerOptions); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs new file mode 100644 index 00000000..660836f9 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs @@ -0,0 +1,324 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Teams.AI.Exceptions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI.Application +{ + + /// + /// A helper class for streaming responses to the client. + /// This class is used to send a series of updates to the client in a single response. The expected + /// sequence of calls is: + /// + /// `QueueInformativeUpdate()`, `QueueTextChunk()`, `QueueTextChunk()`, ..., `EndStream()`. + /// + /// Once `EndStream()` is called, the stream is considered ended and no further updates can be sent. + /// + public class StreamingResponse + { + private readonly ITurnContext _context; + private int _nextSequence = 1; + private bool _ended = false; + + // Queue for outgoing activities + private List> _queue = new(); + private Task? _queueSync; + private bool _chunkQueued = false; + + /// + /// Fluent interface for accessing the attachments. + /// + public List? Attachments { get; set; } = new(); + + /// + /// Sets the Feedback Loop in Teams that allows a user to give thumbs up or down to a response. + /// Defaults to false. + /// + public bool? EnableFeedbackLoop { get; set; } = false; + + /// + /// Sets the "Generated by AI" label in Teams. + /// Defaults to false. + /// + public bool? EnableGeneratedByAILabel { get; set; } = false; + + /// + /// Gets the stream ID of the current response. + /// Assigned after the initial update is sent. + /// + public string? StreamId { get; private set; } + + /// + /// Fluent interface for accessing the message. + /// + public string Message { get; private set; } = ""; + + /// + /// Gets the number of updates sent for the stream. + /// + /// Number of updates sent so far. + public int UpdatesSent() => this._nextSequence - 1; + + /// + /// Creates a new instance of the class. + /// + /// Context for the current turn of conversation with the user. + public StreamingResponse(ITurnContext context) + { + this._context = context; + } + + /// + /// Waits for the outgoing activity queue to be empty. + /// + /// + public Task WaitForQueue() + { + return this._queueSync != null ? this._queueSync : Task.CompletedTask; + } + + /// + /// Queues an informative update to be sent to the client. + /// + /// Text of the update to send. + /// Throws if the stream has already ended. + public void QueueInformativeUpdate(string text) + { + if (this._ended) + { + throw new TeamsAIException("The stream has already ended."); + } + + var activity = new Activity + { + Type = ActivityTypes.Typing, + Text = text, + Entities = [] + }; + + activity.Entities.Add(new StreamInfo() + { + StreamType = StreamType.Informative, + StreamSequence = this._nextSequence++, + }); + + QueueActivity(() => activity); + } + + /// + /// Queues a chunk of partial message text to be sent to the client. + /// + /// Partial text of the message to send. + /// Citations to include in the message. + /// Throws if the stream has already ended. + public void QueueTextChunk(string text) //, IList? citations = null) + { + if (this._ended) + { + throw new TeamsAIException("The stream has already ended."); + } + + Message += text; + + /* + if (citations != null && citations.Count > 0) + { + if (this.Citations == null) + { + this.Citations = new List(); + } + + int currPos = this.Citations.Count; + + foreach (Citation citation in citations) + { + string abs = CitationUtils.Snippet(citation.Content, 480); + + this.Citations.Add(new ClientCitation() + { + Position = $"{currPos}", + Appearance = new ClientCitationAppearance() + { + Name = citation.Title, + Abstract = abs + } + }); + currPos++; + } + + // If there are citations, modify the content so that the sources are numbers instead of [doc1], [doc2], etc. + this.Message = this.Citations.Count == 0 ? this.Message : CitationUtils.FormatCitationsResponse(this.Message); + + // If there are citations, filter out the citations unused in content. + this.Citations = this.Citations.Count > 0 ? CitationUtils.GetUsedCitations(this.Message, this.Citations) : new List(); + + } + */ + + QueueNextChunk(); + } + + /// + /// Ends the stream by sending the final message to the client. + /// + /// A Task representing the async operation + /// Throws if the stream has already ended. + public Task EndStream() + { + if (this._ended) + { + throw new TeamsAIException("The stream has already ended."); + } + + this._ended = true; + QueueNextChunk(); + + // Wait for the queue to drain + return WaitForQueue()!; + } + + /// + /// Queue an activity to be sent to the client. + /// + /// + private void QueueActivity(Func factory) + { + this._queue.Add(factory); + + // If there's no sync in progress, start one + if (this._queueSync == null || this._queueSync.IsCompleted) + { + this._queueSync = DrainQueue(); + + if (this._queueSync.IsFaulted) + { + Exception ex = this._queueSync.Exception; + this._queueSync = null; + throw new TeamsAIException($"Error occurred when sending activity while streaming", ex); + } + } + } + + /// + /// Queue the next chunk of text to be sent to the client. + /// + private void QueueNextChunk() + { + // Check if we are already waiting to send a chunk + if (this._chunkQueued) + { + return; + } + + // Queue a chunk of text to be sent + this._chunkQueued = true; + QueueActivity(() => + { + this._chunkQueued = false; + + if (this._ended) + { + // Send final message + Activity activity = new Activity + { + Type = ActivityTypes.Message, + Text = Message, + Entities = [] + }; + activity.Entities.Add(new StreamInfo + { + StreamType = StreamType.Final, + }); + + if (Attachments != null && Attachments.Count > 0) + { + activity.Attachments = Attachments; + } + return activity; + } + else + { + // Send typing activity + var activity = new Activity + { + Type = ActivityTypes.Typing, + Text = Message, + Entities = [] + }; + activity.Entities.Add(new StreamInfo + { + StreamType = StreamType.Streaming, + StreamSequence = this._nextSequence++, + }); + return activity; + } + }); + } + + /// + /// Sends any queued activities to the client until the queue is empty. + /// + private async Task DrainQueue() + { + try + { + while (this._queue.Count > 0) + { + // Get next activity from queue + Activity activity = _queue[0](); + await SendActivity(activity).ConfigureAwait(false); + _queue.RemoveAt(0); + } + } + catch (Exception) + { + throw; + } + } + + /// + /// Sends an activity to the client and saves the stream ID returned. + /// + /// The activity to send. + /// A Task representing the async operation. + private async Task SendActivity(Activity activity) + { + /* + // Add in Powered by AI feature flags + if (this._ended) + { + // Add in feedback loop + StreamingChannelData currChannelData = activity.GetChannelData(); + currChannelData.feedbackLoopEnabled = EnableFeedbackLoop; + activity.ChannelData = currChannelData; + + // Add in Generated by AI + if (this.EnableGeneratedByAILabel == true) + { + AIEntity entity = new AIEntity(); + if (this.Citations != null && this.Citations.Count > 0) + { + entity.Citation = this.Citations; + } + + entity.UsageInfo = this.SensitivityLabel; + activity.Entities.Add(entity); + } + } + */ + + ResourceResponse response = await this._context.SendActivityAsync(activity).ConfigureAwait(false); + + await Task.Delay(TimeSpan.FromSeconds(1.5)); + + // Save assigned stream ID + if (string.IsNullOrEmpty(StreamId)) + { + StreamId = response.Id; + } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs new file mode 100644 index 00000000..5100d704 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs @@ -0,0 +1,273 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// TaskModules class to enable fluent style registration of handlers related to Task Modules. + /// + /// The type of the turn state object used by the application. + public class TaskModules + where TState : TurnState, new() + { + private static readonly string FETCH_INVOKE_NAME = "task/fetch"; + private static readonly string SUBMIT_INVOKE_NAME = "task/submit"; + private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; + + private readonly Application _app; + + /// + /// Creates a new instance of the TaskModules class. + /// + /// The top level application class to register handlers with. + public TaskModules(Application app) + { + this._app = app; + } + + /// + /// Registers a handler to process the initial fetch of the task module. + /// + /// Name of the verb to register the handler for. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetch(string verb, FetchHandlerAsync handler) + { + Verify.ParamNotNull(verb); + Verify.ParamNotNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, FETCH_INVOKE_NAME); + return OnFetch(routeSelector, handler); + } + + /// + /// Registers a handler to process the initial fetch of the task module. + /// + /// Regular expression to match against the verbs to register the handler for. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) + { + Verify.ParamNotNull(verbPattern); + Verify.ParamNotNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, FETCH_INVOKE_NAME); + return OnFetch(routeSelector, handler); + } + + /// + /// Registers a handler to process the initial fetch of the task module. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + TaskModuleAction? taskModuleAction; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, FETCH_INVOKE_NAME) + || (taskModuleAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + { + throw new TeamsAIException($"Unexpected TaskModules.OnFetch() triggered for activity type: {turnContext.Activity.Type}"); + } + + TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler to process the initial fetch of the task module. + /// + /// Combination of String, Regex, and RouteSelectorAsync selectors. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + + if (routeSelectors.Strings != null) + { + foreach (string verb in routeSelectors.Strings) + { + OnFetch(verb, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex verbPattern in routeSelectors.Regexes) + { + OnFetch(verbPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnFetch(routeSelector, handler); + } + } + + return _app; + } + + /// + /// Registers a handler to process the submission of a task module. + /// + /// Name of the verb to register the handler for. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmit(string verb, SubmitHandlerAsync handler) + { + Verify.ParamNotNull(verb); + Verify.ParamNotNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, SUBMIT_INVOKE_NAME); + return OnSubmit(routeSelector, handler); + } + + + /// + /// Registers a handler to process the submission of a task module. + /// + /// Regular expression to match against the verbs to register the handler for + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) + { + Verify.ParamNotNull(verbPattern); + Verify.ParamNotNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; + RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, SUBMIT_INVOKE_NAME); + return OnSubmit(routeSelector, handler); + } + + /// + /// Registers a handler to process the submission of a task module. + /// + /// Function that's used to select a route. The function returning true triggers the route. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) + { + Verify.ParamNotNull(routeSelector); + Verify.ParamNotNull(handler); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + TaskModuleAction? taskModuleAction; + if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + || !string.Equals(turnContext.Activity.Name, SUBMIT_INVOKE_NAME) + || (taskModuleAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + { + throw new TeamsAIException($"Unexpected TaskModules.OnSubmit() triggered for activity type: {turnContext.Activity.Type}"); + } + + TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return _app; + } + + /// + /// Registers a handler to process the submission of a task module. + /// + /// Combination of String, Regex, and RouteSelectorAsync verb(s) to register the handler for. + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) + { + Verify.ParamNotNull(routeSelectors); + Verify.ParamNotNull(handler); + + if (routeSelectors.Strings != null) + { + foreach (string verb in routeSelectors.Strings) + { + OnSubmit(verb, handler); + } + } + if (routeSelectors.Regexes != null) + { + foreach (Regex verbPattern in routeSelectors.Regexes) + { + OnSubmit(verbPattern, handler); + } + } + if (routeSelectors.RouteSelectors != null) + { + foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) + { + OnSubmit(routeSelector, handler); + } + } + + return _app; + } + + private static RouteSelectorAsync CreateTaskSelector(Func isMatch, string filter, string invokeName) + { + RouteSelectorAsync routeSelector = (ITurnContext turnContext, CancellationToken cancellationToken) => + { + bool isInvoke = string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, invokeName); + if (!isInvoke) + { + return Task.FromResult(false); + } + + //TODO + /* + JObject? obj = turnContext.Activity.Value as JObject; + if (obj == null) + { + return Task.FromResult(false); + } + + JObject? data = obj["data"] as JObject; + if (data == null) + { + return Task.FromResult(false); + } + + bool isVerbMatch = data.TryGetValue(filter, out JToken? filterField) && filterField != null && filterField.Type == JTokenType.String + && isMatch(filterField.Value()!); + + return Task.FromResult(isVerbMatch); + */ + return Task.FromResult(false); + }; + return routeSelector; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs new file mode 100644 index 00000000..2b67ebbf --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs @@ -0,0 +1,31 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Function for handling Task Module fetch events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data associated with the fetch. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An instance of TaskModuleResponse. + public delegate Task FetchHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + + /// + /// Function for handling Task Module submit events. + /// + /// Type of the turn state. This allows for strongly typed access to the turn state. + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// The data associated with the fetch. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An instance of TaskModuleResponse. + public delegate Task SubmitHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs new file mode 100644 index 00000000..2b0cc3fc --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs @@ -0,0 +1,18 @@ +namespace Microsoft.Teams.AI +{ + /// + /// Options for TaskModules class. + /// + public class TaskModulesOptions + { + /// + /// Data field to use to identify the verb of the handler to trigger. + /// + /// + /// When a task module is triggered, the field name specified here will be used to determine + /// the name of the verb for the handler to route the request to. + /// Defaults to a value of "verb". + /// + public string? TaskDataFilter { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs new file mode 100644 index 00000000..60980de2 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs @@ -0,0 +1,26 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Turn event handler to do something before or after a turn is run. Returning false from + /// `beforeTurn` lets you prevent the turn from running and returning false from `afterTurn` + /// lets you prevent the bots state from being saved. + ///
+ ///
+ /// Returning false from `beforeTurn` does result in the bots state being saved which lets you + /// track the reason why the turn was not processed. It also means you can use `beforeTurn` as + /// a way to call into the dialog system. For example, you could use the OAuthPrompt to sign the + /// user in before allowing the AI system to run. + ///
+ /// + /// A strongly-typed context object for this turn. + /// The turn state object that stores arbitrary data for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// True to continue execution of the current turn. Otherwise, False. + public delegate Task TurnEventHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs new file mode 100644 index 00000000..821eadde --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs @@ -0,0 +1,139 @@ +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Teams.AI.Utilities; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Teams.AI +{ + /// + /// Encapsulates the logic for sending "typing" activity to the user. + /// + internal class TypingTimer : IDisposable + { + private Timer? _timer; + /// + /// The interval in milliseconds to send "typing" activity. + /// + private readonly int _interval; + + /// + /// To detect redundant calls + /// + private bool _disposedValue = false; + + /// + /// Constructs a new instance of the class. + /// + /// The interval in milliseconds to send "typing" activity. + public TypingTimer(int interval = 1000) + { + _interval = interval; + } + + /// + /// Manually start a timer to periodically send "typing" activity. + /// + /// + /// The timer will automatically end once an outgoing activity has been sent. If the timer is already running or + /// the current activity is not a "message" the call is ignored. + /// + /// The context for the current turn with the user. + /// True if the timer was started, otherwise False. + public bool Start(ITurnContext turnContext) + { + Verify.ParamNotNull(turnContext); + + if (turnContext.Activity.Type != ActivityTypes.Message || IsRunning()) + { + return false; + } + + // Listen for outgoing activities + turnContext.OnSendActivities(StopTimerWhenSendMessageActivityHandlerAsync); + + // Start periodically send "typing" activity + _timer = new Timer(SendTypingActivity, turnContext, Timeout.Infinite, Timeout.Infinite); + + // Fire first time + _timer.Change(0, Timeout.Infinite); + + return true; + } + + /// + /// Stop the timer that periodically sends "typing" activity. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + + _disposedValue = true; + } + } + + /// + /// Whether there is a timer currently running. + /// + /// True if there's a timer currently running, otherwise False. + public bool IsRunning() + { + return _timer != null; + } + + private async void SendTypingActivity(object state) + { + ITurnContext turnContext = state as ITurnContext ?? throw new ArgumentException("Unexpected failure of casting object TurnContext"); + + try + { + await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }); + if (IsRunning()) + { + _timer?.Change(_interval, Timeout.Infinite); + } + } + catch (Exception e) when (e is ObjectDisposedException || e is TaskCanceledException || e is NullReferenceException) + { + // We're in the middle of sending an activity on a background thread when the turn ends and + // the turn context object is disposed of or the request is cancelled. We can just eat the + // error but lets make sure our states cleaned up a bit. + Dispose(); + } + } + + private Task StopTimerWhenSendMessageActivityHandlerAsync(ITurnContext turnContext, List activities, Func> next) + { + if (_timer != null) + { + foreach (Activity activity in activities) + { + if (activity.Type == ActivityTypes.Message) + { + Dispose(); + break; + } + } + } + + return next(); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs new file mode 100644 index 00000000..dd162e56 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs @@ -0,0 +1,39 @@ + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Teams.AI.Utilities +{ + /// + /// Utility class for verifying arguments and local variables. + /// + internal class Verify + { + /// + /// Verifies that the argument is not null. + /// + /// An arbitrary object. + /// Optional. The name of the parameter. If not populated, it defaults to the name of the variable passed to the parameter. + /// + public static void ParamNotNull(object? argument, [CallerArgumentExpression("argument")] string? parameterName = default) + { + if (argument == null) + { + throw new ArgumentNullException(parameterName); + } + } + + /// + /// Verifies that the local variable is not null. + /// + /// An arbitrary object. + /// + public static void NotNull(object? variable) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj index c8762ceb..a8d1413c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj @@ -19,8 +19,10 @@ + + diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/SearchInvokeValue.cs b/src/libraries/Core/Microsoft.Agents.Core/Models/SearchInvokeValue.cs index 88cbe44e..271a7d41 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/SearchInvokeValue.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Models/SearchInvokeValue.cs @@ -18,7 +18,7 @@ public SearchInvokeValue() /// The query text of this search invoke action value. /// The query options for the query. /// The context information about the query. - internal SearchInvokeValue(string kind, string queryText, object queryOptions, object context) + internal SearchInvokeValue(string kind, string queryText, SearchInvokeOptions queryOptions, object context) { Kind = kind; QueryText = queryText; @@ -31,7 +31,7 @@ internal SearchInvokeValue(string kind, string queryText, object queryOptions, o /// The query text of this search invoke action value. public string QueryText { get; set; } /// The query options for the query. - public object QueryOptions { get; set; } + public SearchInvokeOptions QueryOptions { get; set; } /// The context information about the query. public object Context { get; set; } } diff --git a/src/samples/Application/messaging.echoBot/AspNetExtensions.cs b/src/samples/Application/messaging.echoBot/AspNetExtensions.cs new file mode 100644 index 00000000..f2310e73 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/AspNetExtensions.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Agents.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Protocols; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Validators; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Agents.Samples +{ + public static class AspNetExtensions + { + private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); + + /// + /// Adds token validation typical for ABS/SMBA and Bot-to-bot. + /// default to Azure Public Cloud. + /// + /// + /// + /// Name of the config section to read. + /// Optional logger to use for authentication event logging. + /// + /// Configuration: + /// + /// "TokenValidation": { + /// "Audiences": [ + /// "{required:bot-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// + /// + /// `IsGov` can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. + /// `ValidIssuers` can be omitted, in which case the Public Azure Bot Service issuers are used. + /// `TenantId` can be omitted if the Agent is not being called by another Agent. Otherwise it is used to add other known issuers. Only when `ValidIssuers` is omitted. + /// `AzureBotServiceOpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `OpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `AzureBotServiceTokenHandling` defaults to true and should always be true until Azure Bot Service sends Entra ID token. + /// + public static void AddBotAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string authenticationSection = "TokenValidation", ILogger logger = null) + { + IConfigurationSection tokenValidationSection = configuration.GetSection("TokenValidation"); + + List validTokenIssuers = tokenValidationSection.GetSection("ValidIssuers").Get>(); + + // If ValidIssuers is empty, default for ABS Public Cloud + if (validTokenIssuers == null || validTokenIssuers.Count == 0) + { + validTokenIssuers = + [ + "https://api.botframework.com", + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + ]; + + string tenantId = tokenValidationSection["TenantId"]; + if (!string.IsNullOrEmpty(tenantId)) + { + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId)); + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId)); + } + } + + List audiences = tokenValidationSection.GetSection("Audiences").Get>(); + if (audiences == null || audiences.Count == 0) + { + throw new ArgumentException($"{authenticationSection}:Audiences requires at least one value"); + } + + bool isGov = tokenValidationSection.GetValue("IsGov", false); + var azureBotServiceTokenHandling = tokenValidationSection.GetValue("AzureBotServiceTokenHandling", true); + + // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. + var azureBotServiceOpenIdMetadataUrl = tokenValidationSection["AzureBotServiceOpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(azureBotServiceOpenIdMetadataUrl)) + { + azureBotServiceOpenIdMetadataUrl = isGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; + } + + // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. + var openIdMetadataUrl = tokenValidationSection["OpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(openIdMetadataUrl)) + { + openIdMetadataUrl = isGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; + } + + var openIdRefreshInterval = tokenValidationSection.GetValue("OpenIdMetadataRefresh", BaseConfigurationManager.DefaultAutomaticRefreshInterval); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5), + ValidIssuers = validTokenIssuers, + ValidAudiences = audiences, + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + }; + + // Using Microsoft.IdentityModel.Validators + options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + options.Events = new JwtBearerEvents + { + // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. + OnMessageReceived = async context => + { + var authorizationHeader = context.Request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authorizationHeader)) + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + string[] parts = authorizationHeader?.Split(' '); + if (parts.Length != 2 || parts[0] != "Bearer") + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + JwtSecurityToken token = new(parts[1]); + var issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value; + + if (azureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) + { + // Use the Bot Framework authority for this configuration manager + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(azureBotServiceOpenIdMetadataUrl, key => + { + return new ConfigurationManager(azureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + else + { + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(openIdMetadataUrl, key => + { + return new ConfigurationManager(openIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + + await Task.CompletedTask.ConfigureAwait(false); + }, + + OnTokenValidated = context => + { + logger?.LogDebug("TOKEN Validated"); + return Task.CompletedTask; + }, + OnForbidden = context => + { + logger?.LogWarning(context.Result.ToString()); + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + logger?.LogWarning(context.Exception.ToString()); + return Task.CompletedTask; + } + }; + }); + } + } +} diff --git a/src/samples/Application/messaging.echoBot/BotController.cs b/src/samples/Application/messaging.echoBot/BotController.cs new file mode 100644 index 00000000..70a1ebb4 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace EchoBot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/Application/messaging.echoBot/EchoBot.csproj b/src/samples/Application/messaging.echoBot/EchoBot.csproj new file mode 100644 index 00000000..c520da06 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/EchoBot.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + latest + disable + + + + + + + + + + + + + + Always + + + + diff --git a/src/samples/Application/messaging.echoBot/EchoBot.sln b/src/samples/Application/messaging.echoBot/EchoBot.sln new file mode 100644 index 00000000..b3272ef6 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/EchoBot.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoBot", "EchoBot.csproj", "{D045C9A3-F421-4E8B-91D0-33A62C61DCCD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.Build.0 = Release|Any CPU + {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1A3065E4-A54D-45EE-BDCB-1BADCD6EA7CA} + EndGlobalSection +EndGlobal diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs new file mode 100644 index 00000000..f11610e9 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -0,0 +1,60 @@ +using EchoBot.Model; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Teams.AI; +using Microsoft.Teams.AI.State; +using System.Threading; +using System.Threading.Tasks; + +namespace EchoBot +{ + public class EchoBotApplication : Application + { + public EchoBotApplication(ApplicationOptions options) : base(options) + { + OnConversationUpdate("membersAdded", MembersAddedHandler); + + // Listen for user to say "/reset" and then delete conversation state + OnMessage("/reset", ResetMessageHandler); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, MessageHandler); + } + + /// + /// Handles members added events. + /// + public static async Task MembersAddedHandler(ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + } + + /// + /// Handles "/reset" message. + /// + public static async Task ResetMessageHandler(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + { + turnState.DeleteConversationState(); + await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); + } + + /// + /// Handles messages except "/reset". + /// + public static async Task MessageHandler(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + { + int count = turnState.Conversation.MessageCount; + + // Increment count state. + turnState.Conversation.MessageCount = ++count; + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + } + } +} diff --git a/src/samples/Application/messaging.echoBot/Model/AppState.cs b/src/samples/Application/messaging.echoBot/Model/AppState.cs new file mode 100644 index 00000000..9df25f10 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/Model/AppState.cs @@ -0,0 +1,53 @@ +using Microsoft.Teams.AI.State; +using System; + +namespace EchoBot.Model +{ + // Extend the turn state by configuring custom strongly typed state classes. + public class AppState : TurnState + { + public AppState() : base() + { + ScopeDefaults[CONVERSATION_SCOPE] = new ConversationState(); + } + + /// + /// Stores all the conversation-related state. + /// + public new ConversationState Conversation + { + get + { + TurnStateEntry scope = GetScope(CONVERSATION_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + return (ConversationState)scope.Value!; + } + set + { + TurnStateEntry scope = GetScope(CONVERSATION_SCOPE); + if (scope == null) + { + throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); + } + + scope.Replace(value!); + } + } + } + + // This class adds custom properties to the turn state which will be accessible in the activity handler methods. + public class ConversationState : Record + { + private const string _countKey = "countKey"; + + public int MessageCount + { + get => Get(_countKey); + set => Set(_countKey, value); + } + } +} diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs new file mode 100644 index 00000000..79b557a8 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/Program.cs @@ -0,0 +1,64 @@ +using EchoBot; +using EchoBot.Model; +using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Teams.AI; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Create the storage to persist turn state +builder.Services.AddSingleton(); + +// Add Connections object to access configured token connections. +builder.Services.AddSingleton(); + +// Add factory for ConnectorClient and UserTokenClient creation +builder.Services.AddSingleton(); + +builder.Services.AddCloudAdapter(); + +// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. +builder.Services.AddTransient(sp => +{ + IStorage storage = sp.GetService(); + ApplicationOptions applicationOptions = new() + { + Storage = storage, + TurnStateFactory = () => + { + return new AppState(); + } + }; + + return new EchoBotApplication(applicationOptions); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Copilot SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/src/samples/Application/messaging.echoBot/README.md b/src/samples/Application/messaging.echoBot/README.md new file mode 100644 index 00000000..652fadc8 --- /dev/null +++ b/src/samples/Application/messaging.echoBot/README.md @@ -0,0 +1,29 @@ +# Teams Conversation Bot + +Teams AI Conversation Bot sample for Teams. + +This sample shows how to incorporate basic conversational flow into a Teams application. +It also illustrates a few of the Teams specific calls you can make from your bot. + +## Set up instructions + +All the samples in the C# .NET SDK can be set up in the same way: You can find the step by step instructions here: + [Setup Instructions](../README.md). + +## Interacting with the Bot + +At this point you should have set up the bot and installed it in Teams. You can interact with the bot by sending it a message. + +Here's a sample interaction with the bot: + +![Sample interaction](assets/helloworld.png) + +You can reset the message count by sending the bot the message `/reset`. + +![Reset interaction](assets/reset.png) + +## Deploy to Azure + +You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. + +You can find deployment instructions [here](../README.md#deploy-to-azure). \ No newline at end of file diff --git a/src/samples/Application/messaging.echoBot/appsettings.json b/src/samples/Application/messaging.echoBot/appsettings.json new file mode 100644 index 00000000..e339ac0e --- /dev/null +++ b/src/samples/Application/messaging.echoBot/appsettings.json @@ -0,0 +1,38 @@ +{ + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Assembly": "Microsoft.Agents.Authentication.Msal", + "Type": "MsalAuth", + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file From 5f759c2b61eaa5de00c2f012f12d367bdd25cd56 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 30 Jan 2025 11:12:53 -0600 Subject: [PATCH 04/60] Merged new Agents.State --- .../Application/Application.cs | 32 +--- .../Application/ApplicationOptions.cs | 16 -- .../Core/Microsoft.Agents.State/BotState.cs | 148 +++--------------- .../Core/Microsoft.Agents.State/IBotState.cs | 5 +- .../Core/Microsoft.Agents.State/IMemory.cs | 42 +++++ .../IPropertyManager.cs | 22 --- .../IStatePropertyAccessor.cs | 44 ------ .../IStatePropertyInfo.cs | 19 --- 8 files changed, 68 insertions(+), 260 deletions(-) create mode 100644 src/libraries/Core/Microsoft.Agents.State/IMemory.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs index 9c6e1934..432cd30a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs @@ -19,14 +19,6 @@ namespace Microsoft.Teams.AI /// /// Application class for routing and processing incoming requests. /// - /// - /// The Application object replaces the traditional ActivityHandler that a bot would use. It supports - /// a simpler fluent style of authoring bots versus the inheritance based approach used by the - /// ActivityHandler class. - /// - /// Additionally, it has built-in support for calling into the SDK's AI system and can be used to create - /// bots that leverage Large Language Models (LLM) and other AI capabilities. - /// /// Type of the turnState. This allows for strongly typed access to the turn turnState. public class Application : IBot where TState : TurnState, new() @@ -34,7 +26,6 @@ public class Application : IBot private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; - private readonly ChannelAdapter? _adapter; //TODO //private readonly AuthenticationManager? _authentication; @@ -54,6 +45,7 @@ public class Application : IBot /// Creates a new Application instance. /// /// Optional. Options used to configure the application. + /// public Application(ApplicationOptions options) { Verify.ParamNotNull(options); @@ -65,11 +57,6 @@ public Application(ApplicationOptions options) this.Options.TurnStateFactory = () => new TState(); } - if (Options.Adapter != null) - { - _adapter = Options.Adapter; - } - AdaptiveCards = new AdaptiveCards(this); Meetings = new Meetings(this); MessageExtensions = new MessageExtensions(this); @@ -138,22 +125,6 @@ public AuthenticationManager Authentication } */ - /// - /// Fluent interface for accessing the bot adapter used to configure the application. - /// - public ChannelAdapter Adapter - { - get - { - if (_adapter == null) - { - throw new ArgumentException("The Application.Adapter property is unavailable because it was not configured."); - } - - return _adapter; - } - } - /// /// The application's configured options. /// @@ -1023,7 +994,6 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } } await turnState!.SaveStateAsync(turnContext, storage); - } finally { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs index ae0d8eba..6056bd3f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs @@ -13,22 +13,6 @@ namespace Microsoft.Teams.AI public class ApplicationOptions where TState : TurnState, new() { - /// - /// Optional. Teams Bot adapter being used. - /// - /// - /// If using the option, calling the method, or configuring user authentication, this property is required. - /// - public ChannelAdapter? Adapter { get; set; } - - /// - /// Optional. Application ID of the bot. - /// - /// - /// If using the option, calling the method, or configuring user authentication, this property is required. - /// - public string? BotAppId { get; set; } - /// /// Optional. Storage provider to use for the application. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs index 3504332a..01e6707d 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotState.cs @@ -27,7 +27,7 @@ namespace Microsoft.Agents.State /// You can define additional scopes for your bot. /// /// - public abstract class BotState : IPropertyManager, IBotState + public abstract class BotState : IBotState { private readonly IStorage _storage; private CachedBotState _cachedBotState; @@ -53,21 +53,6 @@ public BotState(IStorage storage, string stateName) public string Name { get; private set; } - /// - /// Creates a named state property within the scope of a and returns - /// an accessor for the property. - /// - /// The value type of the property. - /// The name of the property. - /// An accessor for the property. - /// is null. - [Obsolete("Use BotState.GetValue and BotState.SetValue")] - public IStatePropertyAccessor CreateProperty(string name) - { - ArgumentException.ThrowIfNullOrWhiteSpace(name); - return new BotStatePropertyAccessor(this, name); - } - /// /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// @@ -83,6 +68,17 @@ public void DeleteValue(string name) DeletePropertyValue(name); } + public bool HasValue(string name) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + var cachedState = GetCachedState(); + return ObjectPath.HasValue(cachedState.State, name); + } + /// /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// @@ -303,45 +299,45 @@ internal CachedBotState GetCachedState() /// Gets the value of a property from the state cache for this . /// /// The value type of the property. - /// The name of the property. + /// The path to the property. /// A task that represents the work queued to execute. /// If the task is successful, the result contains the property value, otherwise it will be default(T). #pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) - protected T GetPropertyValue(string propertyName) + protected T GetPropertyValue(string path) #pragma warning restore CA1801 // Review unused parameters { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + ArgumentException.ThrowIfNullOrWhiteSpace(path); var cachedState = GetCachedState(); - return ObjectPath.GetPathValue(cachedState.State, propertyName, true); + return ObjectPath.GetPathValue(cachedState.State, path, true); } /// /// Deletes a property from the state cache for this . /// - /// The name of the property. + /// The name of the property. /// A task that represents the work queued to execute. - protected void DeletePropertyValue(string propertyName) + protected void DeletePropertyValue(string path) { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + ArgumentException.ThrowIfNullOrWhiteSpace(path); var cachedState = GetCachedState(); - cachedState.State.Remove(propertyName); + cachedState.State.Remove(path); } /// /// Sets the value of a property in the state cache for this . /// - /// The name of the property to set. + /// The name of the property to set. /// The value to set on the property. /// A task that represents the work queued to execute. - protected void SetPropertyValue(string propertyName, object value) + protected void SetPropertyValue(string path, object value) { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + ArgumentException.ThrowIfNullOrWhiteSpace(path); var cachedState = GetCachedState(); //cachedState.State[propertyName] = value; - ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); + ObjectPath.SetPathValue(cachedState.State, path, value, false); } /// @@ -391,101 +387,5 @@ internal void Clear() Hash = string.Empty; } } - - #region Obsolete BotStatePropertyAccessor - /// - /// Implements an for a property container. - /// Note the semantics of this accessor are intended to be lazy, this means the Get, Set and Delete - /// methods will first call LoadAsync. This will be a no-op if the data is already loaded. - /// The implication is you can just use this accessor in the application code directly without first calling LoadAsync - /// this approach works with the AutoSaveStateMiddleware which will save as needed at the end of a turn. - /// - /// type of value the propertyAccessor accesses. - private class BotStatePropertyAccessor : IStatePropertyAccessor - { - private BotState _botState; - - public BotStatePropertyAccessor(BotState botState, string name) - { - _botState = botState; - Name = name; - } - - /// - /// Gets name of the property. - /// - /// - /// name of the property. - /// - public string Name { get; private set; } - - /// - /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// The turn context. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - _botState.DeleteValue(Name); - } - - /// - /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// The context object for this turn. - /// Defines the default value. - /// Invoked when no value been set for the requested state property. - /// If defaultValueFactory is defined as null in that case, the method returns null and - /// SetAsync is not called. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) - { - T result = default(T); - - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - - try - { - // if T is a value type, lookup up will throw key not found if not found, but as perf - // optimization it will return null if not found for types which are not value types (string and object). - result = _botState.GetValue(Name, defaultValueFactory); - - if (result == null && defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - catch (KeyNotFoundException) - { - if (defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - - return result; - } - - /// - /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// turn context. - /// value. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken) - { - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - _botState.SetValue(Name, value); - } - } - #endregion } } diff --git a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs index 8f2a89bb..3f5b7a30 100644 --- a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs @@ -8,17 +8,14 @@ namespace Microsoft.Agents.State { - public interface IBotState + public interface IBotState : IMemory { string Name { get; } void ClearState(); Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); - void DeleteValue(string name); - T GetValue(string name, Func defaultValueFactory = null); bool IsLoaded(); Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - void SetValue(string name, T value); } } \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/IMemory.cs b/src/libraries/Core/Microsoft.Agents.State/IMemory.cs new file mode 100644 index 00000000..df701851 --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/IMemory.cs @@ -0,0 +1,42 @@ +using System; + +namespace Microsoft.Agents.State +{ + /// + /// Represents a memory, a key-value store that can be used to store and retrieve values. + /// + public interface IMemory + { + /// + /// Deletes a value from the memory. + /// + /// Path to the value to delete in the form of `[scope].property`. + /// If scope is omitted, the value is deleted from the temporary scope. + void DeleteValue(string path); + + /// + /// Checks if a value exists in the memory. + /// + /// Path to the value to check in the form of `[scope].property`. + /// If scope is omitted, the value is checked in the temporary scope. + /// True if the value exists, false otherwise. + bool HasValue(string path); + + /// + /// Retrieves a value from the memory. + /// + /// Path to the value to retrieve in the form of `[scope].property`. + /// If scope is omitted, the value is retrieved from the temporary scope. + /// Value factory if not found + /// The value or undefined if not found. + T GetValue(string path, Func defaultValueFactory = null); + + /// + /// Assigns a value to the memory. + /// + /// Path to the value to assign in the form of `[scope].property`. + /// If scope is omitted, the value is assigned to the temporary scope. + /// Value to assign. + void SetValue(string path, T value); + } +} diff --git a/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs b/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs deleted file mode 100644 index ba62e082..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.Agents.State -{ - /// - /// IPropertyManager defines implementation of a source of named properties. - /// - public interface IPropertyManager - { - /// - /// Creates a managed state property accessor for a property. - /// - /// The property value type. - /// The name of the property accessor. - /// A state property accessor for the property. - [Obsolete("Use BotState.GetPropertyAsync")] - IStatePropertyAccessor CreateProperty(string name); - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs deleted file mode 100644 index a420108e..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - /// - /// Interface which defines methods for how you can get data from a property source, - /// such as . - /// - /// type of the property. - public interface IStatePropertyAccessor : IStatePropertyInfo - { - /// - /// Gets the property value from the source. - /// - /// Turn Context. - /// Function which defines the property value to be returned if no value has been set. - /// The cancellation token. - /// A representing the result of the asynchronous operation. - Task GetAsync(ITurnContext turnContext, Func defaultValueFactory = null, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Delete the property from the source. - /// - /// Turn Context. - /// The cancellation token. - /// A representing the asynchronous operation. - Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Set the property value on the source. - /// - /// Turn Context. - /// The value to set. - /// The cancellation token. - /// A representing the asynchronous operation. - Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs deleted file mode 100644 index 50ac6103..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.Agents.State -{ - /// - /// Metadata about a property, including policy info. - /// - public interface IStatePropertyInfo - { - /// - /// Gets the name of the property. - /// - /// - /// The name of the property. - /// - string Name { get; } - } -} From 8e2a38687fb156e3e3a68ea4fd6cdce6ccda14cc Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 30 Jan 2025 15:12:49 -0600 Subject: [PATCH 05/60] Minor Application Sample change --- .../messaging.echoBot/EchoBotApplication.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index f11610e9..97bed479 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -12,19 +12,19 @@ public class EchoBotApplication : Application { public EchoBotApplication(ApplicationOptions options) : base(options) { - OnConversationUpdate("membersAdded", MembersAddedHandler); + OnConversationUpdate("membersAdded", WelcomeMessageAsync); // Listen for user to say "/reset" and then delete conversation state - OnMessage("/reset", ResetMessageHandler); + OnMessage("/reset", DeleteStateHandlerAsync); // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - OnActivity(ActivityTypes.Message, MessageHandler); + OnActivity(ActivityTypes.Message, MessageHandlerAsync); } /// /// Handles members added events. /// - public static async Task MembersAddedHandler(ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) + public static async Task WelcomeMessageAsync(ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) { foreach (ChannelAccount member in turnContext.Activity.MembersAdded) { @@ -38,7 +38,7 @@ public static async Task MembersAddedHandler(ITurnContext turnContext, TurnState /// /// Handles "/reset" message. /// - public static async Task ResetMessageHandler(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) { turnState.DeleteConversationState(); await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); @@ -47,7 +47,7 @@ public static async Task ResetMessageHandler(ITurnContext turnContext, AppState /// /// Handles messages except "/reset". /// - public static async Task MessageHandler(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + public static async Task MessageHandlerAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) { int count = turnState.Conversation.MessageCount; From a14272bc2ab49cc48e9e282f48878db6208ff26a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 31 Jan 2025 09:11:16 -0600 Subject: [PATCH 06/60] Split Teams to separate package --- src/Microsoft.Agents.SDK.sln | 17 + .../DialogExtensions.cs | 1 + .../Application/ActivityUtilities.cs | 15 +- .../AdaptiveCardInvokeResponseFactory.cs | 7 +- .../AdaptiveCards/AdaptiveCards.cs | 18 +- .../AdaptiveCards/AdaptiveCardsHandlers.cs | 9 +- .../AdaptiveCards/AdaptiveCardsOptions.cs | 5 +- .../Application/{ => AdaptiveCards}/Query.cs | 11 +- .../Application/Application.cs | 330 +-------------- .../Application/ApplicationBuilder.cs | 20 +- .../Application/ApplicationOptions.cs | 14 +- .../Application/ConversationUpdateEvents.cs | 65 +-- .../Application/Exceptions/AuthException.cs | 7 +- .../Exceptions/InvokeResponseException.cs | 7 +- .../Exceptions/TeamsAIException.cs | 6 +- .../Application/HandoffHandler.cs | 9 +- .../Route/MultipleRouteSelector.cs | 7 +- .../Application/Route/Route.cs | 9 +- .../Application/State/IMemory.cs | 5 +- .../Application/State/ITurnState.cs | 6 +- .../Application/State/MemoryFork.cs | 7 +- .../Application/State/Record.cs | 8 +- .../Application/State/TempState.cs | 17 +- .../Application/State/TurnState.cs | 11 +- .../Application/State/TurnStateEntry.cs | 7 +- .../Application/StreamingResponse.cs | 9 +- .../Application/TurnEventHandlerAsync.cs | 9 +- .../Application/TypingTimer.cs | 8 +- .../Application/Verify.cs | 8 +- .../{ => Compat}/ActivityHandler.cs | 6 +- .../{ => Compat}/BotFrameworkSkillHandler.cs | 4 +- .../NormalizeMentionsMiddleware.cs | 8 +- .../{ => Compat}/ShowTypingMiddleware.cs | 2 +- .../{ => Compat}/TypedTurnContext.cs | 16 +- .../RestChannelServiceClientFactory.cs | 3 +- .../RestClients/RestClientBase.cs | 2 +- .../RestClients/UriExtensions.cs | 2 +- .../Serialization/ProtocolJsonSerializer.cs | 31 +- .../Core/Microsoft.Agents.State/BotState.cs | 148 +++++-- .../Core/Microsoft.Agents.State/IBotState.cs | 5 +- .../Core/Microsoft.Agents.State/IMemory.cs | 42 -- .../IPropertyManager.cs | 22 + .../IStatePropertyAccessor.cs | 44 ++ .../IStatePropertyInfo.cs | 19 + .../TelemetryLoggerMiddleware.cs | 2 +- .../Compat}/SharePointActivityHandler.cs | 4 +- .../SharePointSSOTokenExchangeMiddleware.cs | 4 +- .../Microsoft.Agents.SharePoint.csproj | 37 ++ .../Models/AceData.cs | 2 +- .../Models/AceRequest.cs | 2 +- .../Models/Actions/BaseAction.cs | 2 +- .../Models/Actions/ConfirmationDialog.cs | 2 +- .../Models/Actions/ExecuteAction.cs | 2 +- .../Models/Actions/ExternalLinkAction.cs | 2 +- .../Actions/ExternalLinkActionParameters.cs | 2 +- .../Models/Actions/FocusParameters.cs | 2 +- .../Models/Actions/GetLocationAction.cs | 2 +- .../Actions/GetLocationActionParameters.cs | 2 +- .../Models/Actions/IAction.cs | 2 +- .../Models/Actions/ICardActionParameters.cs | 2 +- .../Models/Actions/IOnCardSelectionAction.cs | 2 +- .../Models/Actions/Location.cs | 2 +- .../Models/Actions/QuickViewAction.cs | 2 +- .../Actions/QuickViewActionParameters.cs | 2 +- .../Models/Actions/SelectMediaAction.cs | 2 +- .../Actions/SelectMediaActionParameters.cs | 2 +- .../Models/Actions/ShowLocationAction.cs | 2 +- .../Actions/ShowLocationActionParameters.cs | 2 +- .../Models/Actions/SubmitAction.cs | 2 +- .../Models/BaseHandleActionResponse.cs | 2 +- .../Models/CardView/BaseCardComponent.cs | 2 +- .../Models/CardView/CardBarComponent.cs | 2 +- .../Models/CardView/CardButtonComponent.cs | 4 +- .../Models/CardView/CardComponentName.cs | 2 +- .../Models/CardView/CardImage.cs | 2 +- .../Models/CardView/CardSearchBoxButton.cs | 4 +- .../Models/CardView/CardSearchBoxComponent.cs | 2 +- .../CardView/CardSearchFooterComponent.cs | 4 +- .../Models/CardView/CardTextComponent.cs | 2 +- .../CardView/CardTextInputBaseButton.cs | 4 +- .../Models/CardView/CardTextInputComponent.cs | 2 +- .../CardView/CardTextInputIconButton.cs | 2 +- .../CardView/CardTextInputTitleButton.cs | 2 +- .../Models/CardView/CardViewParameters.cs | 2 +- .../Models/CardView/ICardButtonBase.cs | 4 +- .../Models/CardViewHandleActionResponse.cs | 2 +- .../Models/CardViewResponse.cs | 6 +- .../GetPropertyPaneConfigurationResponse.cs | 2 +- .../Models/IPropertyPaneFieldProperties.cs | 2 +- .../IPropertyPaneGroupOrConditionalGroup.cs | 2 +- .../Models/NoOpHandleActionResponse.cs | 2 +- .../Models/PropertyPaneCheckboxProperties.cs | 2 +- .../PropertyPaneChoiceGroupIconProperties.cs | 2 +- .../PropertyPaneChoiceGroupImageSize.cs | 2 +- .../Models/PropertyPaneChoiceGroupOption.cs | 2 +- .../PropertyPaneChoiceGroupProperties.cs | 2 +- .../Models/PropertyPaneDropDownOption.cs | 2 +- .../Models/PropertyPaneDropDownProperties.cs | 2 +- .../Models/PropertyPaneGroup.cs | 2 +- .../Models/PropertyPaneGroupField.cs | 2 +- .../Models/PropertyPaneLabelProperties.cs | 2 +- .../PropertyPaneLinkPopupWindowProperties.cs | 2 +- .../Models/PropertyPaneLinkProperties.cs | 2 +- .../Models/PropertyPanePage.cs | 2 +- .../Models/PropertyPanePageHeader.cs | 2 +- .../Models/PropertyPaneSliderProperties.cs | 2 +- .../Models/PropertyPaneTextFieldProperties.cs | 2 +- .../Models/PropertyPaneToggleProperties.cs | 2 +- .../Models/QuickViewData.cs | 2 +- .../Models/QuickViewHandleActionResponse.cs | 2 +- .../Models/QuickViewResponse.cs | 4 +- .../Microsoft.Agents.SharePoint/README.md | 5 + .../Converters/AceDataConverter.cs | 4 +- .../Converters/AceRequestConverter.cs | 4 +- .../Serialization/SerializerExtensions.cs | 19 + .../Application/ConfigHandlerAsync.cs | 8 +- .../Application/FeedbackLoopData.cs | 2 +- .../Application/FeedbackLoopHandler.cs | 6 +- .../Application/FileConsentCardHandler.cs | 8 +- .../Application/IInputFileDownloader.cs | 25 ++ .../Application/InputFile.cs | 2 +- .../Application/Meetings/Meetings.cs | 20 +- .../Application/Meetings/MeetingsHandlers.cs | 8 +- .../MessageExtensions/MessageExtensions.cs | 25 +- .../MessageExtensionsHandlers.cs | 9 +- .../O365ConnectorCardActionHandler.cs | 8 +- .../Application/ReadReceiptHandler.cs | 8 +- .../Application/TaskModules/TaskModules.cs | 42 +- .../TaskModules/TaskModulesHandlers.cs | 8 +- .../TaskModules/TaskModulesOptions.cs | 2 +- .../Application/TeamsApplication.cs | 395 ++++++++++++++++++ .../TeamsConversationUpdateEvents.cs | 78 ++++ .../AttachmentExtensions.cs | 4 +- .../Compat}/TeamsActivityHandler.cs | 6 +- .../TeamsSSOTokenExchangeMiddleware.cs | 2 +- .../Connector}/ITeamsConnectorClient.cs | 4 +- .../Connector}/ITeamsOperations.cs | 4 +- .../Connector}/RestTeamsConnectorClient.cs | 3 +- .../Connector}/RestTeamsOperations.cs | 4 +- .../Connector}/RetryAction.cs | 2 +- .../Connector}/RetryParams.cs | 2 +- .../Connector}/TeamsInfo.cs | 17 +- .../Connector}/ThrottleException.cs | 2 +- .../Connector}/TimeSpanExtensions.cs | 2 +- .../Microsoft.Agents.Teams.csproj | 38 ++ .../Models/AppBasedLinkQuery.cs | 2 +- .../Models/BatchFailedEntriesResponse.cs | 2 +- .../Models/BatchFailedEntry.cs | 2 +- .../Models/BatchOperationState.cs | 2 +- .../Models/BotConfigAuth.cs | 2 +- .../Models/CacheInfo.cs | 2 +- .../Models/ChannelInfo.cs | 2 +- .../Models/ConfigAuthResponse.cs | 2 +- .../Models/ConfigResponse.cs | 2 +- .../Models/ConfigResponseBase.cs | 2 +- .../Models/ConfigTaskResponse.cs | 2 +- .../Models/ContentType.cs | 2 +- .../Models/ConversationList.cs | 2 +- .../Models/FileConsentCard.cs | 2 +- .../Models/FileConsentCardResponse.cs | 2 +- .../Models/FileDownloadInfo.cs | 2 +- .../Models/FileInfoCard.cs | 2 +- .../Models/FileUploadInfo.cs | 2 +- .../Models/MeetingDetails.cs | 2 +- .../Models/MeetingDetailsBase.cs | 2 +- .../Models/MeetingEndEventDetails.cs | 2 +- .../Models/MeetingEventDetails.cs | 2 +- .../Models/MeetingInfo.cs | 2 +- .../Models/MeetingNotification.cs | 2 +- .../Models/MeetingNotificationBase.cs | 2 +- .../Models/MeetingNotificationChannelData.cs | 2 +- ...MeetingNotificationRecipientFailureInfo.cs | 2 +- .../Models/MeetingNotificationResponse.cs | 2 +- .../Models/MeetingParticipantInfo.cs | 2 +- .../Models/MeetingParticipantsEventDetails.cs | 2 +- .../Models/MeetingStageSurface.cs | 2 +- .../Models/MeetingStartEventDetails.cs | 2 +- .../Models/MeetingTabIconSurface.cs | 2 +- .../Models/MessageActionsPayload.cs | 2 +- .../Models/MessageActionsPayloadApp.cs | 2 +- .../Models/MessageActionsPayloadAttachment.cs | 2 +- .../Models/MessageActionsPayloadBody.cs | 2 +- .../MessageActionsPayloadConversation.cs | 2 +- .../Models/MessageActionsPayloadFrom.cs | 2 +- .../Models/MessageActionsPayloadMention.cs | 2 +- .../Models/MessageActionsPayloadReaction.cs | 2 +- .../Models/MessageActionsPayloadUser.cs | 2 +- .../Models/MessagingExtensionAction.cs | 2 +- .../MessagingExtensionActionResponse.cs | 2 +- .../Models/MessagingExtensionAttachment.cs | 2 +- .../Models/MessagingExtensionParameter.cs | 2 +- .../Models/MessagingExtensionQuery.cs | 2 +- .../Models/MessagingExtensionQueryOptions.cs | 2 +- .../Models/MessagingExtensionResponse.cs | 2 +- .../Models/MessagingExtensionResult.cs | 2 +- .../MessagingExtensionSuggestedAction.cs | 2 +- .../Models/NotificationInfo.cs | 2 +- .../Models/O365ConnectorCard.cs | 2 +- .../Models/O365ConnectorCardActionBase.cs | 2 +- .../Models/O365ConnectorCardActionCard.cs | 2 +- .../Models/O365ConnectorCardActionQuery.cs | 2 +- .../Models/O365ConnectorCardDateInput.cs | 2 +- .../Models/O365ConnectorCardFact.cs | 2 +- .../Models/O365ConnectorCardHttpPOST.cs | 2 +- .../Models/O365ConnectorCardImage.cs | 2 +- .../Models/O365ConnectorCardInputBase.cs | 2 +- .../O365ConnectorCardMultichoiceInput.cs | 2 +- ...O365ConnectorCardMultichoiceInputChoice.cs | 2 +- .../Models/O365ConnectorCardOpenUri.cs | 2 +- .../Models/O365ConnectorCardOpenUriTarget.cs | 2 +- .../Models/O365ConnectorCardSection.cs | 2 +- .../Models/O365ConnectorCardTextInput.cs | 2 +- .../Models/O365ConnectorCardViewAction.cs | 2 +- .../Models/OnBehalfOf.cs | 2 +- .../Models/ReadReceiptInfo.cs | 2 +- .../Models/SigninStateVerificationQuery.cs | 2 +- .../Microsoft.Agents.Teams}/Models/Surface.cs | 2 +- .../Models/SurfaceType.cs | 2 +- .../Models/TabContext.cs | 2 +- .../Models/TabEntityContext.cs | 2 +- .../Models/TabRequest.cs | 2 +- .../Models/TabResponse.cs | 2 +- .../Models/TabResponseCard.cs | 2 +- .../Models/TabResponseCards.cs | 2 +- .../Models/TabResponsePayload.cs | 2 +- .../Models/TabSubmit.cs | 2 +- .../Models/TabSubmitData.cs | 2 +- .../Models/TabSuggestedActions.cs | 2 +- .../Models/TargetedMeetingNotification.cs | 2 +- .../TargetedMeetingNotificationValue.cs | 2 +- .../Models/TaskModuleAction.cs | 2 +- .../Models/TaskModuleCardResponse.cs | 2 +- .../Models/TaskModuleContinueResponse.cs | 2 +- .../Models/TaskModuleMessageResponse.cs | 2 +- .../Models/TaskModuleRequest.cs | 2 +- .../Models/TaskModuleRequestContext.cs | 2 +- .../Models/TaskModuleResponse.cs | 2 +- .../Models/TaskModuleResponseBase.cs | 2 +- .../Models/TaskModuleTaskInfo.cs | 2 +- .../Models/TeamDetails.cs | 2 +- .../Models/TeamInfo.cs | 2 +- .../Models/TeamMember.cs | 2 +- .../Models/TeamsChannelAccount.cs | 2 +- .../Models/TeamsChannelData.cs | 2 +- .../Models/TeamsChannelDataSettings.cs | 2 +- .../Models/TeamsMeetingInfo.cs | 2 +- .../Models/TeamsMeetingMember.cs | 2 +- .../Models/TeamsMeetingParticipant.cs | 2 +- .../Models/TeamsPagedMembersResult.cs | 2 +- .../Models/TeamsParticipantChannelAccount.cs | 2 +- .../Models/TenantInfo.cs | 2 +- .../Models/UserMeetingDetails.cs | 2 +- .../Partner/Microsoft.Agents.Teams/README.md | 5 + ...ssagingExtensionActionResponseConverter.cs | 4 +- .../MessagingExtensionAttachmentConverter.cs | 4 +- .../Converters/SurfaceConverter.cs | 4 +- .../Converters/TabSubmitDataConverter.cs | 4 +- .../TaskModuleCardResponseConverter.cs | 4 +- .../TaskModuleContinueResponseConverter.cs | 4 +- .../TaskModuleMessageResponseConverter.cs | 4 +- .../TaskModuleResponseBaseConverter.cs | 4 +- .../Converters/TaskModuleResponseConverter.cs | 4 +- .../Converters/TeamsChannelDataConverter.cs | 4 +- .../Serialization/SerializerExtensions.cs | 27 ++ .../TeamsActivityExtensions.cs | 4 +- .../TeamsChannelServiceClientFactory.cs | 109 +++++ .../messaging.echoBot/EchoBotApplication.cs | 4 +- .../messaging.echoBot/Model/AppState.cs | 2 +- .../Application/messaging.echoBot/Program.cs | 5 +- src/samples/AuthenticationBot/AuthBot.cs | 1 + src/samples/BotToBot/Bot1/Bots/Bot1.cs | 1 + src/samples/BotToBot/Bot2/Bots/Bot2.cs | 2 +- .../CopilotStudioBot.cs | 2 +- src/samples/EchoBot/MyBot.cs | 2 +- .../SemanticKernel/WeatherBot/MyBot.cs | 2 +- .../Bots/TeamsConversationBot.cs | 2 +- .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 2 +- .../Bots/InMeetingNotifications.cs | 2 +- .../Bots/TeamsMessagingExtensionsSearchBot.cs | 2 +- .../TaskModule/Bots/TeamsTaskModuleBot.cs | 2 +- .../Models/TaskModuleResponseFactory.cs | 2 +- .../Teams/bot-all-cards/Bots/DialogBot.cs | 2 +- .../bot-tag-mention/Dialogs/MainDialog.cs | 2 +- .../DialogExtensionsTests.cs | 1 + .../Bots/DialogBot.cs | 1 + .../Bots/MyBot.cs | 1 + .../Bots/ProactiveBot.cs | 1 + .../Debugging/DebugBot.cs | 2 + .../ActivityHandlerTests.cs | 1 + .../ChannelServiceClientFactoryTests.cs | 2 +- .../SharePointActivityHandlerTests.cs | 2 +- .../Teams/TeamsActivityHandlerTests.cs | 2 +- .../Teams/TestActivityHandler.cs | 2 +- .../BotFrameworkSkillHandlerTests.cs | 1 + .../Teams/RestTeamsOperationsTests.cs | 4 +- .../Teams/RetryActionTests.cs | 2 +- .../Teams/RetryParamTests.cs | 2 +- .../HostedActivityServiceTests.cs | 2 +- .../ServiceCollectionExtensionsTests.cs | 1 + .../AppBasedLinkQueryTests.cs | 2 +- .../AttachmentExtensionsTests.cs | 2 +- .../BotConfigAuthTests.cs | 2 +- .../CacheInfoTests.cs | 2 +- .../ChannelInfoTests.cs | 2 +- .../ConfigAuthResponseTests.cs | 2 +- .../ConfigResponseTests.cs | 2 +- .../ConfigTaskResponseTests.cs | 2 +- .../ConversationListTests.cs | 2 +- .../FileConsentCardResponseTests.cs | 2 +- .../FileConsentCardTests.cs | 2 +- .../FileDownloadInfoTests.cs | 2 +- .../FileInfoCardTests.cs | 2 +- .../FileUploadInfoTests.cs | 2 +- .../MeetingParticipantInfoTests.cs | 2 +- .../MeetingParticipantsEventDetailsTests.cs | 2 +- .../MessageActionsPayloadAppTests.cs | 2 +- .../MessageActionsPayloadAttachmentTests.cs | 2 +- .../MessageActionsPayloadBodyTests.cs | 2 +- ...MessageActionsPayloadConversationsTests.cs | 2 +- .../MessageActionsPayloadFromTests.cs | 2 +- .../MessageActionsPayloadMentionTests.cs | 2 +- .../MessageActionsPayloadReactionTests.cs | 2 +- .../MessageActionsPayloadTests.cs | 2 +- .../MessageActionsPayloadUserTests.cs | 2 +- .../MessagingExtensionActionResponseTests.cs | 2 +- .../MessagingExtensionActionTests.cs | 2 +- .../MessagingExtensionAttachmentTests.cs | 2 +- .../MessagingExtensionParametersTests.cs | 2 +- .../MessagingExtensionQueryOptionsTests.cs | 2 +- .../MessagingExtensionQueryTests.cs | 2 +- .../MessagingExtensionResponseTests.cs | 2 +- .../MessagingExtensionResultTests.cs | 2 +- .../MessagingExtensionSuggestedActionTests.cs | 2 +- .../NotificationInfoTests.cs | 2 +- .../O365ConnectorCardActionBaseTests.cs | 2 +- .../O365ConnectorCardActionCardTests.cs | 2 +- .../O365ConnectorCardActionQueryTests.cs | 2 +- .../O365ConnectorCardDateInputTests.cs | 2 +- .../O365ConnectorCardFactTests.cs | 2 +- .../O365ConnectorCardHttpPOSTTests.cs | 2 +- .../O365ConnectorCardImageTests.cs | 2 +- .../O365ConnectorCardInputBaseTests.cs | 2 +- ...onnectorCardMultichoiceInputChoiceTests.cs | 2 +- .../O365ConnectorCardMultichoiceInputTests.cs | 2 +- .../O365ConnectorCardOpenUriTargetTests.cs | 2 +- .../O365ConnectorCardOpenUriTests.cs | 2 +- .../O365ConnectorCardSectionTests.cs | 2 +- .../O365ConnectorCardTests.cs | 2 +- .../O365ConnectorCardTextInputTests.cs | 2 +- .../O365ConnectorCardViewActionTests.cs | 2 +- .../ReadReceiptInfoTests.cs | 2 +- .../SigninStateVerificationQueryTests.cs | 2 +- .../TabContextTests.cs | 2 +- .../TabEntityContextTests.cs | 2 +- .../TabRequestTests.cs | 2 +- .../TabResponseCardTests.cs | 2 +- .../TabResponseCardsTests.cs | 2 +- .../TabResponsePayloadTests.cs | 2 +- .../TabResponseTests.cs | 2 +- .../TabSubmitDataTests.cs | 2 +- .../TabSubmitTests.cs | 2 +- .../TabSuggestedActionsTests.cs | 2 +- .../TabsTestData.cs | 2 +- .../TaskModuleActionTests.cs | 2 +- .../TaskModuleCardResponseTests.cs | 2 +- .../TaskModuleContinueResponseTests.cs | 2 +- .../TaskModuleMessageResponseTests.cs | 2 +- .../TaskModuleRequestContextTests.cs | 2 +- .../TaskModuleRequestTests.cs | 2 +- .../TaskModuleResponseBaseTests.cs | 2 +- .../TaskModuleResponseTests.cs | 2 +- .../TaskModuleTaskInfoTests.cs | 2 +- .../TeamDetailsTests.cs | 2 +- .../TeamInfoTests.cs | 2 +- .../TeamsChannelAccountTests.cs | 2 +- .../TeamsChannelDataTests.cs | 2 +- .../TeamsMeetingInfoTests.cs | 2 +- .../TeamsMeetingParticipantTests.cs | 2 +- .../TeamsPagedMembersResultTests.cs | 2 +- .../TeamsParticipantChannelAccountTests.cs | 2 +- .../TenantInfoTests.cs | 2 +- 381 files changed, 1589 insertions(+), 1009 deletions(-) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/{ => AdaptiveCards}/Query.cs (81%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => Compat}/ActivityHandler.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => Compat}/BotFrameworkSkillHandler.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => Compat}/NormalizeMentionsMiddleware.cs (97%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => Compat}/ShowTypingMiddleware.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => Compat}/TypedTurnContext.cs (93%) delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IMemory.cs create mode 100644 src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs create mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs create mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint => Partner/Microsoft.Agents.SharePoint/Compat}/SharePointActivityHandler.cs (98%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint => Partner/Microsoft.Agents.SharePoint/Compat}/SharePointSSOTokenExchangeMiddleware.cs (98%) create mode 100644 src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/AceData.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/AceRequest.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/BaseAction.cs (90%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ConfirmationDialog.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ExecuteAction.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ExternalLinkAction.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ExternalLinkActionParameters.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/FocusParameters.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/GetLocationAction.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/GetLocationActionParameters.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/IAction.cs (86%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ICardActionParameters.cs (87%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/IOnCardSelectionAction.cs (87%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/Location.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/QuickViewAction.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/QuickViewActionParameters.cs (92%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/SelectMediaAction.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/SelectMediaActionParameters.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ShowLocationAction.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/ShowLocationActionParameters.cs (92%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/Actions/SubmitAction.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/BaseHandleActionResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/BaseCardComponent.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardBarComponent.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardButtonComponent.cs (92%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardComponentName.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardImage.cs (91%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardSearchBoxButton.cs (84%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardSearchBoxComponent.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardSearchFooterComponent.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardTextComponent.cs (91%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardTextInputBaseButton.cs (84%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardTextInputComponent.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardTextInputIconButton.cs (88%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardTextInputTitleButton.cs (88%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/CardViewParameters.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardView/ICardButtonBase.cs (85%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardViewHandleActionResponse.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/CardViewResponse.cs (89%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/GetPropertyPaneConfigurationResponse.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/IPropertyPaneFieldProperties.cs (89%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/IPropertyPaneGroupOrConditionalGroup.cs (89%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/NoOpHandleActionResponse.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneCheckboxProperties.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneChoiceGroupIconProperties.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneChoiceGroupImageSize.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneChoiceGroupOption.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneChoiceGroupProperties.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneDropDownOption.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneDropDownProperties.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneGroup.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneGroupField.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneLabelProperties.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneLinkPopupWindowProperties.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneLinkProperties.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPanePage.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPanePageHeader.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneSliderProperties.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneTextFieldProperties.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/PropertyPaneToggleProperties.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/QuickViewData.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/QuickViewHandleActionResponse.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Models/QuickViewResponse.cs (96%) create mode 100644 src/libraries/Partner/Microsoft.Agents.SharePoint/README.md rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Serialization/Converters/AceDataConverter.cs (86%) rename src/libraries/{Core/Microsoft.Agents.Core/Sharepoint => Partner/Microsoft.Agents.SharePoint}/Serialization/Converters/AceRequestConverter.cs (86%) create mode 100644 src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/ConfigHandlerAsync.cs (84%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/FeedbackLoopData.cs (95%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/FeedbackLoopHandler.cs (87%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/FileConsentCardHandler.cs (85%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/InputFile.cs (95%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/Meetings/Meetings.cs (85%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/Meetings/MeetingsHandlers.cs (93%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/MessageExtensions/MessageExtensions.cs (97%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/MessageExtensions/MessageExtensionsHandlers.cs (97%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/O365ConnectorCardActionHandler.cs (85%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/ReadReceiptHandler.cs (83%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/TaskModules/TaskModules.cs (91%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/TaskModules/TaskModulesHandlers.cs (90%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder => Partner/Microsoft.Agents.Teams}/Application/TaskModules/TaskModulesOptions.cs (90%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/AttachmentExtensions.cs (95%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Teams => Partner/Microsoft.Agents.Teams/Compat}/TeamsActivityHandler.cs (99%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Teams => Partner/Microsoft.Agents.Teams/Compat}/TeamsSSOTokenExchangeMiddleware.cs (99%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/ITeamsConnectorClient.cs (86%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/ITeamsOperations.cs (99%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/RestTeamsConnectorClient.cs (92%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/RestTeamsOperations.cs (99%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/RetryAction.cs (98%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/RetryParams.cs (98%) rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Teams => Partner/Microsoft.Agents.Teams/Connector}/TeamsInfo.cs (97%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/ThrottleException.cs (97%) rename src/libraries/{Client/Microsoft.Agents.Connector/Teams => Partner/Microsoft.Agents.Teams/Connector}/TimeSpanExtensions.cs (97%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/AppBasedLinkQuery.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/BatchFailedEntriesResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/BatchFailedEntry.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/BatchOperationState.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/BotConfigAuth.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/CacheInfo.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ChannelInfo.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ConfigAuthResponse.cs (90%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ConfigResponse.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ConfigResponseBase.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ConfigTaskResponse.cs (91%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ContentType.cs (91%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ConversationList.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/FileConsentCard.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/FileConsentCardResponse.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/FileDownloadInfo.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/FileInfoCard.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/FileUploadInfo.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingDetails.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingDetailsBase.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingEndEventDetails.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingEventDetails.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingInfo.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingNotification.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingNotificationBase.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingNotificationChannelData.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingNotificationRecipientFailureInfo.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingNotificationResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingParticipantInfo.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingParticipantsEventDetails.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingStageSurface.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingStartEventDetails.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MeetingTabIconSurface.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayload.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadApp.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadAttachment.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadBody.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadConversation.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadFrom.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadMention.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadReaction.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessageActionsPayloadUser.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionAction.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionActionResponse.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionAttachment.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionParameter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionQuery.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionQueryOptions.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionResult.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/MessagingExtensionSuggestedAction.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/NotificationInfo.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCard.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardActionBase.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardActionCard.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardActionQuery.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardDateInput.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardFact.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardHttpPOST.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardImage.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardInputBase.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardMultichoiceInput.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardMultichoiceInputChoice.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardOpenUri.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardOpenUriTarget.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardSection.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardTextInput.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/O365ConnectorCardViewAction.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/OnBehalfOf.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/ReadReceiptInfo.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/SigninStateVerificationQuery.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/Surface.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/SurfaceType.cs (92%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabContext.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabEntityContext.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabRequest.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabResponse.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabResponseCard.cs (93%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabResponseCards.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabResponsePayload.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabSubmit.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabSubmitData.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TabSuggestedActions.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TargetedMeetingNotification.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TargetedMeetingNotificationValue.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleAction.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleCardResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleContinueResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleMessageResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleRequest.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleRequestContext.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleResponse.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleResponseBase.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TaskModuleTaskInfo.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamDetails.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamInfo.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamMember.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsChannelAccount.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsChannelData.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsChannelDataSettings.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsMeetingInfo.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsMeetingMember.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsMeetingParticipant.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsPagedMembersResult.cs (97%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TeamsParticipantChannelAccount.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/TenantInfo.cs (95%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Models/UserMeetingDetails.cs (94%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/README.md rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/MessagingExtensionActionResponseConverter.cs (80%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/MessagingExtensionAttachmentConverter.cs (80%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/SurfaceConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TabSubmitDataConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TaskModuleCardResponseConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TaskModuleContinueResponseConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TaskModuleMessageResponseConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TaskModuleResponseBaseConverter.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TaskModuleResponseConverter.cs (94%) rename src/libraries/{Core/Microsoft.Agents.Core/Teams => Partner/Microsoft.Agents.Teams}/Serialization/Converters/TeamsChannelDataConverter.cs (96%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs rename src/libraries/{BotBuilder/Microsoft.Agents.BotBuilder/Teams => Partner/Microsoft.Agents.Teams}/TeamsActivityExtensions.cs (98%) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 7e791988..b7e48368 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -132,6 +132,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{CB471290-FA93-4109-8A65-37F0DE9EDB2C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Partner", "Partner", "{8BA20AC9-A0C4-4386-AF9A-E48D870E965D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Teams", "libraries\Partner\Microsoft.Agents.Teams\Microsoft.Agents.Teams.csproj", "{81F8DD27-1795-4D80-9C7D-681CC495B38F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.SharePoint", "libraries\Partner\Microsoft.Agents.SharePoint\Microsoft.Agents.SharePoint.csproj", "{20C15862-9867-4929-8E7C-D46FAD6AAF40}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -340,6 +346,14 @@ Global {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.Build.0 = Release|Any CPU {CB471290-FA93-4109-8A65-37F0DE9EDB2C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {81F8DD27-1795-4D80-9C7D-681CC495B38F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81F8DD27-1795-4D80-9C7D-681CC495B38F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81F8DD27-1795-4D80-9C7D-681CC495B38F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81F8DD27-1795-4D80-9C7D-681CC495B38F}.Release|Any CPU.Build.0 = Release|Any CPU + {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -405,6 +419,9 @@ Global {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {A2AEDED1-5F24-4084-BB93-8B3E3D490FFA} = {674A812C-7287-4883-97F9-697D83750648} {CB471290-FA93-4109-8A65-37F0DE9EDB2C} = {A2AEDED1-5F24-4084-BB93-8B3E3D490FFA} + {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} + {81F8DD27-1795-4D80-9C7D-681CC495B38F} = {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} + {20C15862-9867-4929-8E7C-D46FAD6AAF40} = {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index e7055570..d2f09215 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -14,6 +14,7 @@ using Microsoft.Agents.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs index 2a00298f..bff81ad2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs @@ -1,16 +1,13 @@ -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Serialization; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Models; using System.Net; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { - internal static class ActivityUtilities + public static class ActivityUtilities { - public static T? GetTypedValue(IActivity activity) - { - return ProtocolJsonSerializer.ToObject(activity.Value); - } - public static Activity CreateInvokeResponseActivity(object? body = default) { Activity activity = new() diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs index 7d88b439..5b8c8b4c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs @@ -1,7 +1,10 @@ -using AdaptiveCards; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AdaptiveCards; using Microsoft.Agents.Core.Models; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards { /// /// Contains utility methods for creating various types of . diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs index 84426ef4..3ba0564c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs @@ -1,16 +1,18 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.Exceptions; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Teams.AI.Exceptions; -using Microsoft.Teams.AI.State; -using Microsoft.Teams.AI.Utilities; using System; using System.Collections.Generic; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards { /// /// Constants for adaptive card invoke names @@ -88,7 +90,7 @@ public Application OnActionExecute(RouteSelectorAsync routeSelector, Act AdaptiveCardInvokeValue? invokeValue; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, AdaptiveCardsInvokeNames.ACTION_INVOKE_NAME) - || (invokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || (invokeValue = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null || invokeValue.Action == null || !string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE)) { @@ -337,7 +339,7 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand AdaptiveCardSearchInvokeValue? searchInvokeValue; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME) - || (searchInvokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + || (searchInvokeValue = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { throw new TeamsAIException($"Unexpected AdaptiveCards.OnSearch() triggered for activity type: {turnContext.Activity.Type}"); } @@ -408,7 +410,7 @@ private static RouteSelectorAsync CreateActionExecuteSelector(Func return Task.FromResult( string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, AdaptiveCardsInvokeNames.ACTION_INVOKE_NAME) - && (invokeValue = ActivityUtilities.GetTypedValue(turnContext.Activity)) != null + && (invokeValue = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) != null && invokeValue.Action != null && string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE) && isMatch(invokeValue.Action.Verb)); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs index abca7647..2732435c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs @@ -1,11 +1,14 @@ -using Microsoft.Agents.Core.Interfaces; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Teams.AI.State; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards { /// /// Parameters passed to AdaptiveCards.OnSearch() handler. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs index dead50f6..73d5981c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Teams.AI +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards { /// /// Options for AdaptiveCards class. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs similarity index 81% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs index cd41f03b..b4e43fae 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Query.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Teams.AI +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards { /// /// Query arguments for Message Extension query and Adaptive Card dynamic search. @@ -29,9 +32,9 @@ public class Query /// Query parameters. public Query(int count, int skip, TParams parameters) { - this.Count = count; - this.Skip = skip; - this.Parameters = parameters; + Count = count; + Skip = skip; + Parameters = parameters; } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs index 432cd30a..0f4046e9 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs @@ -1,20 +1,19 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; using Microsoft.Agents.Storage; -using Microsoft.Teams.AI.Application; -using Microsoft.Teams.AI.State; -using Microsoft.Teams.AI.Utilities; using System; using System.Collections.Concurrent; -using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { /// /// Application class for routing and processing incoming requests. @@ -23,9 +22,6 @@ namespace Microsoft.Teams.AI public class Application : IBot where TState : TurnState, new() { - private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; - private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; - //TODO //private readonly AuthenticationManager? _authentication; @@ -58,9 +54,6 @@ public Application(ApplicationOptions options) } AdaptiveCards = new AdaptiveCards(this); - Meetings = new Meetings(this); - MessageExtensions = new MessageExtensions(this); - TaskModules = new TaskModules(this); _routes = new ConcurrentQueue>(); _invokeRoutes = new ConcurrentQueue>(); @@ -90,21 +83,6 @@ public Application(ApplicationOptions options) /// public AdaptiveCards AdaptiveCards { get; } - /// - /// Fluent interface for accessing Meetings' specific features. - /// - public Meetings Meetings { get; } - - /// - /// Fluent interface for accessing Message Extensions' specific features. - /// - public MessageExtensions MessageExtensions { get; } - - /// - /// Fluent interface for accessing Task Modules' specific features. - /// - public TaskModules TaskModules { get; } - //TODO /* /// @@ -246,28 +224,14 @@ public Application OnActivity(MultipleRouteSelector routeSelectors, Rout /// Name of the conversation update event to handle, can use . /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + public virtual Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) { Verify.ParamNotNull(conversationUpdateEvent); Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector; switch (conversationUpdateEvent) { - case ConversationUpdateEvents.ChannelCreated: - case ConversationUpdateEvents.ChannelDeleted: - case ConversationUpdateEvents.ChannelRenamed: - case ConversationUpdateEvents.ChannelRestored: - { - routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.ChannelId, Channels.Msteams) - && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) - && context.Activity?.GetChannelData()?.Channel != null - && context.Activity?.GetChannelData()?.Team != null - ); - break; - } case ConversationUpdateEvents.MembersAdded: { routeSelector = (context, _) => Task.FromResult @@ -288,29 +252,11 @@ public Application OnConversationUpdate(string conversationUpdateEvent, ); break; } - case ConversationUpdateEvents.TeamRenamed: - case ConversationUpdateEvents.TeamDeleted: - case ConversationUpdateEvents.TeamHardDeleted: - case ConversationUpdateEvents.TeamArchived: - case ConversationUpdateEvents.TeamUnarchived: - case ConversationUpdateEvents.TeamRestored: - { - routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.ChannelId, Channels.Msteams) - && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) - && context.Activity?.GetChannelData()?.Team != null - ); - break; - } default: { routeSelector = (context, _) => Task.FromResult ( - string.Equals(context.Activity?.ChannelId, Channels.Msteams) - && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) ); break; } @@ -446,69 +392,6 @@ public Application OnMessage(MultipleRouteSelector routeSelectors, Route return this; } - /// - /// Handles message edit events. - /// - /// Function to call when the event is triggered. - /// The application instance for chaining purposes. - public Application OnMessageEdit(RouteHandler handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => - { - TeamsChannelData teamsChannelData; - return Task.FromResult( - string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) - && (teamsChannelData = turnContext.Activity.GetChannelData()) != null - && string.Equals(teamsChannelData.EventType, "editMessage")); - }; - AddRoute(routeSelector, handler, isInvokeRoute: false); - return this; - } - - /// - /// Handles message undo soft delete events. - /// - /// Function to call when the event is triggered. - /// The application instance for chaining purposes. - public Application OnMessageUndelete(RouteHandler handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => - { - TeamsChannelData teamsChannelData; - return Task.FromResult( - string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) - && (teamsChannelData = turnContext.Activity.GetChannelData()) != null - && string.Equals(teamsChannelData.EventType, "undeleteMessage")); - }; - AddRoute(routeSelector, handler, isInvokeRoute: false); - return this; - } - - /// - /// Handles message soft delete events. - /// - /// Function to call when the event is triggered. - /// The application instance for chaining purposes. - public Application OnMessageDelete(RouteHandler handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => - { - TeamsChannelData teamsChannelData; - return Task.FromResult( - string.Equals(turnContext.Activity.Type, ActivityTypes.MessageDelete, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) - && (teamsChannelData = turnContext.Activity.GetChannelData()) != null - && string.Equals(teamsChannelData.EventType, "softDeleteMessage")); - }; - AddRoute(routeSelector, handler, isInvokeRoute: false); - return this; - } - /// /// Handles message reactions added events. /// @@ -545,158 +428,6 @@ public Application OnMessageReactionsRemoved(RouteHandler handle return this; } - /// - /// Handles read receipt events for messages sent by the bot in personal scope. - /// - /// Function to call when the route is triggered. - /// The application instance for chaining purposes. - public Application OnTeamsReadReceipt(ReadReceiptHandler handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.ChannelId, Channels.Msteams) - && string.Equals(context.Activity?.Name, "application/vnd.microsoft.readReceipt") - ); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => - { - ReadReceiptInfo readReceiptInfo = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); - await handler(turnContext, turnState, readReceiptInfo, cancellationToken); - }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: false); - return this; - } - - /// - /// Handles config fetch events for Microsoft Teams. - /// - /// Function to call when the event is triggered. - /// The application instance for chaining purposes. - public Application OnConfigFetch(ConfigHandlerAsync handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( - string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.Name, CONFIG_FETCH_INVOKE_NAME) - && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => - { - ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); - - // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); - await turnContext.SendActivityAsync(activity, cancellationToken); - } - }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); - return this; - } - - /// - /// Handles config submit events for Microsoft Teams. - /// - /// Function to call when the event is triggered. - /// The application instance for chaining purposes. - public Application OnConfigSubmit(ConfigHandlerAsync handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( - string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.Name, CONFIG_SUBMIT_INVOKE_NAME) - && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => - { - ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); - - // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); - await turnContext.SendActivityAsync(activity, cancellationToken); - } - }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); - return this; - } - - /// - /// Handles when a file consent card is accepted by the user. - /// - /// Function to call when the route is triggered. - /// The application instance for chaining purposes. - public Application OnFileConsentAccept(FileConsentHandler handler) - => OnFileConsent(handler, "accept"); - - /// - /// Handles when a file consent card is declined by the user. - /// - /// Function to call when the route is triggered. - /// The application instance for chaining purposes. - public Application OnFileConsentDecline(FileConsentHandler handler) - => OnFileConsent(handler, "decline"); - - private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (context, _) => - { - FileConsentCardResponse? fileConsentCardResponse; - return Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.Name, "fileConsent/invoke") - && (fileConsentCardResponse = ActivityUtilities.GetTypedValue(context.Activity!)) != null - && string.Equals(fileConsentCardResponse.Action, fileConsentAction) - ); - }; - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => - { - FileConsentCardResponse fileConsentCardResponse = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); - await handler(turnContext, turnState, fileConsentCardResponse, cancellationToken); - - // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); - await turnContext.SendActivityAsync(activity, cancellationToken); - } - }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); - return this; - } - - /// - /// Handles O365 Connector Card Action activities. - /// - /// Function to call when the route is triggered. - /// The application instance for chaining purposes. - public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) - { - Verify.ParamNotNull(handler); - RouteSelectorAsync routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.Name, "actionableMessage/executeAction") - ); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => - { - O365ConnectorCardActionQuery query = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); - await handler(turnContext, turnState, query, cancellationToken); - - // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); - await turnContext.SendActivityAsync(activity, cancellationToken); - } - }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); - return this; - } - /// /// Handles handoff activities. /// @@ -726,47 +457,6 @@ public Application OnHandoff(HandoffHandler handler) return this; } - /// - /// Registers a handler for feedback loop events when a user clicks the thumbsup or thumbsdown button on a response sent from the AI module. - /// must be set to true. - /// - /// Function to cal lwhen the route is triggered - /// - public Application OnFeedbackLoop(FeedbackLoopHandler handler) - { - Verify.ParamNotNull(handler); - - RouteSelectorAsync routeSelector = (context, _) => - { - var jsonObject = ProtocolJsonSerializer.ToObject(context.Activity.Value); - string? actionName = jsonObject.ContainsKey("actionName") ? jsonObject["actionName"].ToString() : string.Empty; - return Task.FromResult - ( - context.Activity.Type == ActivityTypes.Invoke - && context.Activity.Name == "message/submitAction" - && actionName == "feedback" - ); - }; - - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => - { - FeedbackLoopData feedbackLoopData = ActivityUtilities.GetTypedValue(turnContext.Activity)!; - feedbackLoopData.ReplyToId = turnContext.Activity.ReplyToId; - - await handler(turnContext, turnState, feedbackLoopData, cancellationToken); - - // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); - await turnContext.SendActivityAsync(activity, cancellationToken); - } - }; - - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); - return this; - } - /// /// Add a handler that will execute before the turn's activity handler logic is processed. ///
diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs index bbcf2a89..f4553140 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs @@ -1,10 +1,13 @@ -using Microsoft.Teams.AI.State; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Extensions.Logging; -using Microsoft.Agents.BotBuilder; using System; using Microsoft.Agents.Storage; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { /// /// A builder class for simplifying the creation of an Application instance. @@ -62,17 +65,6 @@ public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions a return this; } - /// - /// Configures the processing of Task Module requests. - /// - /// The options for Task Modules. - /// The ApplicationBuilder instance. - public ApplicationBuilder WithTaskModuleOptions(TaskModulesOptions taskModulesOptions) - { - Options.TaskModules = taskModulesOptions; - return this; - } - /// /// Configures the removing of mentions of the bot's name from incoming messages. /// Default state for removeRecipientMention is true diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs index 6056bd3f..147440cf 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs @@ -1,10 +1,13 @@ -using Microsoft.Teams.AI.State; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Extensions.Logging; using System; -using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Storage; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { /// /// Options for the class. @@ -23,11 +26,6 @@ public class ApplicationOptions /// public AdaptiveCardsOptions? AdaptiveCards { get; set; } - /// - /// Optional. Options used to customize the processing of Task Modules requests. - /// - public TaskModulesOptions? TaskModules { get; set; } - /// /// Optional. Factory used to create a custom turn state instance. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs index 2a2b97b5..c32c2b1d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs @@ -1,30 +1,13 @@ -namespace Microsoft.Teams.AI +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.Application { /// /// Conversation update events. /// public static class ConversationUpdateEvents { - /// - /// ChannelCreated event - /// - public const string ChannelCreated = "channelCreated"; - - /// - /// ChannelRenamed event - /// - public const string ChannelRenamed = "channelRenamed"; - - /// - /// ChannelDeleted event - /// - public const string ChannelDeleted = "channelDeleted"; - - /// - /// ChannelRestored event - /// - public const string ChannelRestored = "channelRestored"; - /// /// MembersAdded event /// @@ -34,45 +17,5 @@ public static class ConversationUpdateEvents /// MembersRemoved event /// public const string MembersRemoved = "membersRemoved"; - - /// - /// TeamRenamed event - /// - public const string TeamRenamed = "teamRenamed"; - - /// - /// TeamDeleted event - /// - public const string TeamDeleted = "teamDeleted"; - - /// - /// TeamArchived event - /// - public const string TeamArchived = "teamArchived"; - - /// - /// TeamUnarchived event - /// - public const string TeamUnarchived = "teamUnarchived"; - - /// - /// TeamRestored event - /// - public const string TeamRestored = "teamRestored"; - - /// - /// TeamHardDeleted event - /// - public const string TeamHardDeleted = "teamHardDeleted"; - - /// - /// TopicName event - /// - public const string TopicName = "topicName"; - - /// - /// HistoryDisclosed event - /// - public const string HistoryDisclosed = "historyDisclosed"; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs index ce0a2a46..be99abf3 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs @@ -1,6 +1,9 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -namespace Microsoft.Teams.AI.Exceptions +using System; + +namespace Microsoft.Agents.BotBuilder.Application.Exceptions { /// /// Cause of user authentication exception. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs index f36f522b..1b6a95a6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs @@ -1,7 +1,10 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Net; -namespace Microsoft.Teams.AI.Exceptions +namespace Microsoft.Agents.BotBuilder.Application.Exceptions { /// /// A custom exception for invoke response errors. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs index 1bd1a9e7..87680324 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs @@ -1,7 +1,9 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using System; -namespace Microsoft.Teams.AI.Exceptions +namespace Microsoft.Agents.BotBuilder.Application.Exceptions { /// /// Base exception for the TeamsAI library. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs index 5f7bfd7e..59d23ea1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs @@ -1,9 +1,12 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Teams.AI.State; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI.Application +namespace Microsoft.Agents.BotBuilder.Application { /// /// Function for handling handoff activities. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs index 503e3591..48dc7a23 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs @@ -1,6 +1,9 @@ -using System.Text.RegularExpressions; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -namespace Microsoft.Teams.AI +using System.Text.RegularExpressions; + +namespace Microsoft.Agents.BotBuilder.Application.Route { /// /// Combination of String, Regex, and RouteSelectorAsync selectors. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs index b686fc17..b982872a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs @@ -1,9 +1,12 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Teams.AI.State; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application.Route { /// /// Function for selecting whether a route handler should be triggered. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs index f2bfd3b9..19d314f9 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Teams.AI.State +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// Represents a memory, a key-value store that can be used to store and retrieve values. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs index cec545fa..7a897fb3 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs @@ -1,10 +1,12 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Storage; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// The turn state interface. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs index 9db86d35..48a887c4 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs @@ -1,8 +1,11 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.Linq; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs index 16972328..d53c5c42 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs @@ -1,9 +1,11 @@ -using Microsoft.Agents.Core.Serialization; -using Microsoft.Teams.AI.Utilities; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; using System; using System.Collections.Generic; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// The class representing a record. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs index 442d18b0..321cbf8d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs @@ -1,8 +1,9 @@ - -using Microsoft.Teams.AI.Application; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using System.Collections.Generic; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// Temporary state. @@ -52,7 +53,6 @@ public TempState() : base() this[ActionOutputsKey] = new Dictionary(); this[AuthTokenKey] = new Dictionary(); this[DuplicateTokenExchangeKey] = false; - this[InputFilesKey] = new List(); } /// @@ -100,14 +100,5 @@ public bool DuplicateTokenExchange get => Get(DuplicateTokenExchangeKey)!; set => Set(DuplicateTokenExchangeKey, value); } - - /// - /// Downloaded files passed by the user to the AI library - /// - public List InputFiles - { - get => Get>(InputFilesKey)!; - set => Set(InputFilesKey, value); - } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs index 87c717a8..b90fa0cc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs @@ -1,15 +1,16 @@ -using Microsoft.Agents.Core.Interfaces; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.Exceptions; +using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Storage; -using Microsoft.Teams.AI.Exceptions; -using Microsoft.Teams.AI.Utilities; using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// Base class defining a collection of turn state scopes. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs index 7d980fe8..c896837b 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs @@ -1,8 +1,9 @@ -using Microsoft.Teams.AI.Utilities; -using System.Runtime.CompilerServices; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using System.Text.Json; -namespace Microsoft.Teams.AI.State +namespace Microsoft.Agents.BotBuilder.Application.State { /// /// Accessor class for managing an individual state scope. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs index 660836f9..adc94e95 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs @@ -1,11 +1,14 @@ -using Microsoft.Agents.Core.Interfaces; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.Exceptions; +using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Teams.AI.Exceptions; using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Teams.AI.Application +namespace Microsoft.Agents.BotBuilder.Application { /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs index 60980de2..c2e6928c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs @@ -1,9 +1,12 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Teams.AI.State; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { /// /// Turn event handler to do something before or after a turn is run. Returning false from diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs index 821eadde..200d23d7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs @@ -1,12 +1,14 @@ -using Microsoft.Agents.Core.Interfaces; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Teams.AI.Utilities; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.BotBuilder.Application { /// /// Encapsulates the logic for sending "typing" activity to the user. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs index dd162e56..48835248 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs @@ -1,13 +1,15 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using System; using System.Runtime.CompilerServices; -namespace Microsoft.Teams.AI.Utilities +namespace Microsoft.Agents.BotBuilder.Application { /// /// Utility class for verifying arguments and local variables. /// - internal class Verify + public class Verify { /// /// Verifies that the argument is not null. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ActivityHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ActivityHandler.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs index daa2232f..391ee1c6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ActivityHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.Compat { /// /// An implementation of the interface, intended for further subclassing. @@ -71,7 +71,7 @@ public virtual async Task OnTurnAsync(ITurnContext turnContext, CancellationToke case ActivityTypes.Message: await OnMessageActivityAsync(new TypedTurnContext(turnContext), cancellationToken).ConfigureAwait(false); break; - + case ActivityTypes.MessageUpdate: await OnMessageUpdateActivityAsync(new TypedTurnContext(turnContext), cancellationToken).ConfigureAwait(false); break; @@ -793,7 +793,7 @@ private static SearchInvokeValue GetSearchInvokeValue(IInvokeActivity activity) } SearchInvokeValue invokeValue = null; - + try { invokeValue = ProtocolJsonSerializer.ToObject(activity.Value); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotFrameworkSkillHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotFrameworkSkillHandler.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs index 5d726d2e..6dc30a4d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotFrameworkSkillHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs @@ -16,7 +16,7 @@ using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Connector; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.Compat { /// /// This is the Bot Framework SDK implementation of IChannelApiHandler for handling Skill requests. @@ -45,7 +45,7 @@ public BotFrameworkSkillHandler( _bot = bot; _adapter = adapter; - _conversationIdFactory = conversationIdFactory; + _conversationIdFactory = conversationIdFactory; _logger = logger ?? NullLogger.Instance; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/NormalizeMentionsMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/NormalizeMentionsMiddleware.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs index b827f652..ff2e22ed 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/NormalizeMentionsMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.Compat { /// /// Middleware to normalize mention Entities from channels that apply <at> markup tags since they don't conform to expected values. @@ -41,7 +41,7 @@ public NormalizeMentionsMiddleware() /// next middleware. /// cancellationToken. /// A representing the asynchronous operation. - public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) { NormalizeActivity(turnContext.Activity); await next(cancellationToken).ConfigureAwait(false); @@ -55,7 +55,7 @@ private void NormalizeActivity(IActivity activity) { if (activity.Type == ActivityTypes.Message) { - if (this.RemoveRecipientMention) + if (RemoveRecipientMention) { // strip recipient mention tags and text. activity.RemoveRecipientMention(); @@ -63,7 +63,7 @@ private void NormalizeActivity(IActivity activity) if (activity.Entities != null) { // strip entity.mention records for recipient id. - activity.Entities = activity.Entities.Where(entity => entity is Mention mention && + activity.Entities = activity.Entities.Where(entity => entity is Mention mention && mention.Mentioned.Id != activity.Recipient.Id).ToList(); } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ShowTypingMiddleware.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index e214608a..c4702cfd 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -12,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.Compat { /// /// When added, this middleware will send typing activities back to the user when a Message activity diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TypedTurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs similarity index 93% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TypedTurnContext.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs index 02412ec9..62ad9d1a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TypedTurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.Compat { /// /// A TurnContext with a strongly typed Activity property that wraps an untyped inner TurnContext. @@ -59,11 +59,11 @@ internal class TypedTurnContext(ITurnContext innerTurnContext) : ITurnContext IActivity ITurnContext.Activity => _innerTurnContext.Activity; /// - public Task DeleteActivityAsync(string activityId, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteActivityAsync(string activityId, CancellationToken cancellationToken = default) => _innerTurnContext.DeleteActivityAsync(activityId, cancellationToken); /// - public Task DeleteActivityAsync(ConversationReference conversationReference, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteActivityAsync(ConversationReference conversationReference, CancellationToken cancellationToken = default) => _innerTurnContext.DeleteActivityAsync(conversationReference, cancellationToken); /// @@ -79,23 +79,23 @@ public ITurnContext OnUpdateActivity(UpdateActivityHandler handler) => _innerTurnContext.OnUpdateActivity(handler); /// - public Task SendActivitiesAsync(IActivity[] activities, CancellationToken cancellationToken = default(CancellationToken)) + public Task SendActivitiesAsync(IActivity[] activities, CancellationToken cancellationToken = default) => _innerTurnContext.SendActivitiesAsync(activities, cancellationToken); /// - public Task SendActivityAsync(string textReplyToSend, string speak = null, string inputHint = InputHints.AcceptingInput, CancellationToken cancellationToken = default(CancellationToken)) + public Task SendActivityAsync(string textReplyToSend, string speak = null, string inputHint = InputHints.AcceptingInput, CancellationToken cancellationToken = default) => _innerTurnContext.SendActivityAsync(textReplyToSend, speak, inputHint, cancellationToken); /// - public Task SendActivityAsync(IActivity activity, CancellationToken cancellationToken = default(CancellationToken)) + public Task SendActivityAsync(IActivity activity, CancellationToken cancellationToken = default) => _innerTurnContext.SendActivityAsync(activity, cancellationToken); /// - public Task UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken = default) => _innerTurnContext.UpdateActivityAsync(activity, cancellationToken); /// - public Task TraceActivityAsync(string name, object value = null, string valueType = null, [CallerMemberName] string label = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task TraceActivityAsync(string name, object value = null, string valueType = null, [CallerMemberName] string label = null, CancellationToken cancellationToken = default) => _innerTurnContext.TraceActivityAsync(name, value, valueType, label, cancellationToken); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/RestChannelServiceClientFactory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/RestChannelServiceClientFactory.cs index 2caac997..1c1cea45 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/RestChannelServiceClientFactory.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/RestChannelServiceClientFactory.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.Agents.Authentication; using Microsoft.Agents.Connector; -using Microsoft.Agents.Connector.Teams; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -72,7 +71,7 @@ public Task CreateConnectorClientAsync(ClaimsIdentity claimsId ArgumentException.ThrowIfNullOrWhiteSpace(audience); // Intentionally create the TeamsConnectorClient since it supports the same operations as for ABS plus the Teams operations. - return Task.FromResult(new RestTeamsConnectorClient( + return Task.FromResult(new RestConnectorClient( new Uri(serviceUrl), _httpClientFactory, useAnonymous ? null : () => diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs index f8de94d2..c3f625b6 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs @@ -8,7 +8,7 @@ namespace Microsoft.Agents.Connector.RestClients { - internal class RestClientBase(IHttpClientFactory httpClientFactory, string httpClientName, Func> tokenProviderFunction) + public class RestClientBase(IHttpClientFactory httpClientFactory, string httpClientName, Func> tokenProviderFunction) { private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); private readonly Func> _tokenProviderFunction = tokenProviderFunction; diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UriExtensions.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UriExtensions.cs index f75f32b9..8c3934b7 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UriExtensions.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UriExtensions.cs @@ -5,7 +5,7 @@ namespace Microsoft.Agents.Connector.RestClients { - internal static class UriExtensions + public static class UriExtensions { public static Uri AppendQuery(this Uri uri, string name, string value, bool escape = true) { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs index 94557b64..ff8811e1 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs @@ -7,9 +7,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.SharePoint.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Agents.Core.Teams.Serialization.Converters; namespace Microsoft.Agents.Core.Serialization { @@ -24,9 +21,7 @@ public static class ProtocolJsonSerializer public static JsonSerializerOptions CreateConnectorOptions() { var options = new JsonSerializerOptions() - .ApplyCoreOptions() - .ApplyTeamsOptions() - .ApplySharepointOptions(); + .ApplyCoreOptions(); return options; } @@ -61,30 +56,6 @@ private static JsonSerializerOptions ApplyCoreOptions(this JsonSerializerOptions return options; } - private static JsonSerializerOptions ApplyTeamsOptions(this JsonSerializerOptions options) - { - options.Converters.Add(new SurfaceConverter()); - options.Converters.Add(new TabSubmitDataConverter()); - options.Converters.Add(new TeamsChannelDataConverter()); - options.Converters.Add(new MessagingExtensionActionResponseConverter()); - options.Converters.Add(new TaskModuleResponseConverter()); - options.Converters.Add(new TaskModuleResponseBaseConverter()); - options.Converters.Add(new TaskModuleCardResponseConverter()); - options.Converters.Add(new TaskModuleContinueResponseConverter()); - options.Converters.Add(new TaskModuleMessageResponseConverter()); - options.Converters.Add(new MessagingExtensionAttachmentConverter()); - - return options; - } - - private static JsonSerializerOptions ApplySharepointOptions(this JsonSerializerOptions options) - { - options.Converters.Add(new AceDataConverter()); - options.Converters.Add(new AceRequestConverter()); - - return options; - } - /// /// Decompose an object into its constituent JSON elements. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs index 01e6707d..3504332a 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotState.cs @@ -27,7 +27,7 @@ namespace Microsoft.Agents.State /// You can define additional scopes for your bot. /// /// - public abstract class BotState : IBotState + public abstract class BotState : IPropertyManager, IBotState { private readonly IStorage _storage; private CachedBotState _cachedBotState; @@ -53,6 +53,21 @@ public BotState(IStorage storage, string stateName) public string Name { get; private set; } + /// + /// Creates a named state property within the scope of a and returns + /// an accessor for the property. + /// + /// The value type of the property. + /// The name of the property. + /// An accessor for the property. + /// is null. + [Obsolete("Use BotState.GetValue and BotState.SetValue")] + public IStatePropertyAccessor CreateProperty(string name) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return new BotStatePropertyAccessor(this, name); + } + /// /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// @@ -68,17 +83,6 @@ public void DeleteValue(string name) DeletePropertyValue(name); } - public bool HasValue(string name) - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - var cachedState = GetCachedState(); - return ObjectPath.HasValue(cachedState.State, name); - } - /// /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// @@ -299,45 +303,45 @@ internal CachedBotState GetCachedState() /// Gets the value of a property from the state cache for this . /// /// The value type of the property. - /// The path to the property. + /// The name of the property. /// A task that represents the work queued to execute. /// If the task is successful, the result contains the property value, otherwise it will be default(T). #pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) - protected T GetPropertyValue(string path) + protected T GetPropertyValue(string propertyName) #pragma warning restore CA1801 // Review unused parameters { - ArgumentException.ThrowIfNullOrWhiteSpace(path); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); var cachedState = GetCachedState(); - return ObjectPath.GetPathValue(cachedState.State, path, true); + return ObjectPath.GetPathValue(cachedState.State, propertyName, true); } /// /// Deletes a property from the state cache for this . /// - /// The name of the property. + /// The name of the property. /// A task that represents the work queued to execute. - protected void DeletePropertyValue(string path) + protected void DeletePropertyValue(string propertyName) { - ArgumentException.ThrowIfNullOrWhiteSpace(path); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); var cachedState = GetCachedState(); - cachedState.State.Remove(path); + cachedState.State.Remove(propertyName); } /// /// Sets the value of a property in the state cache for this . /// - /// The name of the property to set. + /// The name of the property to set. /// The value to set on the property. /// A task that represents the work queued to execute. - protected void SetPropertyValue(string path, object value) + protected void SetPropertyValue(string propertyName, object value) { - ArgumentException.ThrowIfNullOrWhiteSpace(path); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); var cachedState = GetCachedState(); //cachedState.State[propertyName] = value; - ObjectPath.SetPathValue(cachedState.State, path, value, false); + ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); } /// @@ -387,5 +391,101 @@ internal void Clear() Hash = string.Empty; } } + + #region Obsolete BotStatePropertyAccessor + /// + /// Implements an for a property container. + /// Note the semantics of this accessor are intended to be lazy, this means the Get, Set and Delete + /// methods will first call LoadAsync. This will be a no-op if the data is already loaded. + /// The implication is you can just use this accessor in the application code directly without first calling LoadAsync + /// this approach works with the AutoSaveStateMiddleware which will save as needed at the end of a turn. + /// + /// type of value the propertyAccessor accesses. + private class BotStatePropertyAccessor : IStatePropertyAccessor + { + private BotState _botState; + + public BotStatePropertyAccessor(BotState botState, string name) + { + _botState = botState; + Name = name; + } + + /// + /// Gets name of the property. + /// + /// + /// name of the property. + /// + public string Name { get; private set; } + + /// + /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// The turn context. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + _botState.DeleteValue(Name); + } + + /// + /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// The context object for this turn. + /// Defines the default value. + /// Invoked when no value been set for the requested state property. + /// If defaultValueFactory is defined as null in that case, the method returns null and + /// SetAsync is not called. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) + { + T result = default(T); + + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + + try + { + // if T is a value type, lookup up will throw key not found if not found, but as perf + // optimization it will return null if not found for types which are not value types (string and object). + result = _botState.GetValue(Name, defaultValueFactory); + + if (result == null && defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); + } + } + catch (KeyNotFoundException) + { + if (defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); + } + } + + return result; + } + + /// + /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// turn context. + /// value. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken) + { + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + _botState.SetValue(Name, value); + } + } + #endregion } } diff --git a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs index 3f5b7a30..8f2a89bb 100644 --- a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs @@ -8,14 +8,17 @@ namespace Microsoft.Agents.State { - public interface IBotState : IMemory + public interface IBotState { string Name { get; } void ClearState(); Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); + void DeleteValue(string name); + T GetValue(string name, Func defaultValueFactory = null); bool IsLoaded(); Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + void SetValue(string name, T value); } } \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/IMemory.cs b/src/libraries/Core/Microsoft.Agents.State/IMemory.cs deleted file mode 100644 index df701851..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IMemory.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace Microsoft.Agents.State -{ - /// - /// Represents a memory, a key-value store that can be used to store and retrieve values. - /// - public interface IMemory - { - /// - /// Deletes a value from the memory. - /// - /// Path to the value to delete in the form of `[scope].property`. - /// If scope is omitted, the value is deleted from the temporary scope. - void DeleteValue(string path); - - /// - /// Checks if a value exists in the memory. - /// - /// Path to the value to check in the form of `[scope].property`. - /// If scope is omitted, the value is checked in the temporary scope. - /// True if the value exists, false otherwise. - bool HasValue(string path); - - /// - /// Retrieves a value from the memory. - /// - /// Path to the value to retrieve in the form of `[scope].property`. - /// If scope is omitted, the value is retrieved from the temporary scope. - /// Value factory if not found - /// The value or undefined if not found. - T GetValue(string path, Func defaultValueFactory = null); - - /// - /// Assigns a value to the memory. - /// - /// Path to the value to assign in the form of `[scope].property`. - /// If scope is omitted, the value is assigned to the temporary scope. - /// Value to assign. - void SetValue(string path, T value); - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs b/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs new file mode 100644 index 00000000..ba62e082 --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Agents.State +{ + /// + /// IPropertyManager defines implementation of a source of named properties. + /// + public interface IPropertyManager + { + /// + /// Creates a managed state property accessor for a property. + /// + /// The property value type. + /// The name of the property accessor. + /// A state property accessor for the property. + [Obsolete("Use BotState.GetPropertyAsync")] + IStatePropertyAccessor CreateProperty(string name); + } +} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs new file mode 100644 index 00000000..a420108e --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Agents.Core.Interfaces; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.State +{ + /// + /// Interface which defines methods for how you can get data from a property source, + /// such as . + /// + /// type of the property. + public interface IStatePropertyAccessor : IStatePropertyInfo + { + /// + /// Gets the property value from the source. + /// + /// Turn Context. + /// Function which defines the property value to be returned if no value has been set. + /// The cancellation token. + /// A representing the result of the asynchronous operation. + Task GetAsync(ITurnContext turnContext, Func defaultValueFactory = null, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Delete the property from the source. + /// + /// Turn Context. + /// The cancellation token. + /// A representing the asynchronous operation. + Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Set the property value on the source. + /// + /// Turn Context. + /// The value to set. + /// The cancellation token. + /// A representing the asynchronous operation. + Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs new file mode 100644 index 00000000..50ac6103 --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.Agents.State +{ + /// + /// Metadata about a property, including policy info. + /// + public interface IStatePropertyInfo + { + /// + /// Gets the name of the property. + /// + /// + /// The name of the property. + /// + string Name { get; } + } +} diff --git a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs b/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs index 06e0e44d..5903b92e 100644 --- a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs +++ b/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointActivityHandler.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs similarity index 98% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointActivityHandler.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs index 4022feab..70f82d55 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointActivityHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs @@ -4,13 +4,13 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.SharePoint.Models; +using Microsoft.Agents.SharePoint.Models; using System; using System.Net; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.SharePoint +namespace Microsoft.Agents.SharePoint { /// /// The SharePointActivityHandler is derived from ActivityHandler. It adds support for diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs similarity index 98% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointSSOTokenExchangeMiddleware.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs index a7e8fd7d..7fc8c9b8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Sharepoint/SharePointSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.SharePoint.Models; +using Microsoft.Agents.SharePoint.Models; using System; using System.Collections.Generic; using System.Net; @@ -14,7 +14,7 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector; -namespace Microsoft.Agents.BotBuilder.SharePoint +namespace Microsoft.Agents.SharePoint { /// /// If the activity name is cardExtension/token, this middleware will attempt to diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj b/src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj new file mode 100644 index 00000000..2b3b801a --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj @@ -0,0 +1,37 @@ + + + + latest + CplSharePoint + true + README.md + + + + + Microsoft.Agents.SharePoint + Library for creating SharePoint agents using Microsoft Agent SDK + Library for building SharePoint agents using Microsoft Agents SDK + + + + annotations + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceData.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceData.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs index 7faaa4f0..46914976 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceData.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint Ace Data object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceRequest.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceRequest.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs index 0ab77927..4dcd6dbd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/AceRequest.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// ACE invoke request payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/BaseAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs similarity index 90% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/BaseAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs index 1f88e877..e9d74a30 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/BaseAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Base Action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ConfirmationDialog.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ConfirmationDialog.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs index 92fb7d82..17cd44cd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ConfirmationDialog.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint Confirmation Dialog object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExecuteAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExecuteAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs index 0ac9e678..94cb5afa 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExecuteAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Action.Execute. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs index cce213e7..bdea0f2d 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint external link action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs index fd538d3b..7815b3e9 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ExternalLinkActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint parameters for an External Link action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/FocusParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/FocusParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs index c3c3553f..f2471fad 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/FocusParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs @@ -3,7 +3,7 @@ using System.Runtime.Serialization; -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint focus parameters. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs index d974539c..53d48cfd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint get location action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs index 9c65004d..2c8a025a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/GetLocationActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint parameters for a Get Location action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs similarity index 86% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs index 53d9157a..711938f1 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Interface for actions. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ICardActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs similarity index 87% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ICardActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs index befd7dac..fa38c40d 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ICardActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Interface for card action parameters. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IOnCardSelectionAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs similarity index 87% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IOnCardSelectionAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs index 8ef7d58a..46f4b67d 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/IOnCardSelectionAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Interface for action upon card selection. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/Location.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/Location.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs index 16b99c25..6c13fd28 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/Location.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Sharepoint Location object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs index 4ff779bc..17cfa107 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint Quick View action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs similarity index 92% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs index e6dcb0f3..d58501f7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/QuickViewActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint parameters for an quick view action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs index 9220662a..ab5874ce 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint select media action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs index 3cbc0bc8..93700dd4 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SelectMediaActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint parameters for a select media action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs index 77b06d24..797e8ded 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint show location action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationActionParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs similarity index 92% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationActionParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs index 9b179f1e..7659d676 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/ShowLocationActionParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// SharePoint parameters for a show location action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SubmitAction.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SubmitAction.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs index 57e4f616..6def9f8a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/Actions/SubmitAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models.Actions +namespace Microsoft.Agents.SharePoint.Models.Actions { /// /// Action.Submit. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/BaseHandleActionResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/BaseHandleActionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs index b689448c..d5e10f1b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/BaseHandleActionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Adaptive Card Extension View response type. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/BaseCardComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/BaseCardComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs index e4054221..5c87748e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/BaseCardComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Base class for Adaptive Card Extensions card view components. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardBarComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardBarComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs index b49c6356..60866722 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardBarComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension card bar component. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardButtonComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs similarity index 92% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardButtonComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs index a3cadb85..81ec6861 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardButtonComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Names of the supported Adaptive Card Extension Card View button styles. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardComponentName.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardComponentName.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs index 6f2b48ff..6a8ee4a3 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardComponentName.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Names of the supported Adaptive Card Extension Card View Components. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardImage.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs similarity index 91% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardImage.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs index 1c870811..f5c1d2b6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardImage.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Properties for the image rendered in a card view. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxButton.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs similarity index 84% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxButton.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs index c88f20b6..771a053f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxButton.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Card Search box button. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs index 6a650a47..63cdc547 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchBoxComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension search box component. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchFooterComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchFooterComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs index f49f6bc0..54a7db75 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardSearchFooterComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; using System; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension search footer component. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs similarity index 91% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs index 0fb69662..f657b573 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension card text component. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputBaseButton.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs similarity index 84% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputBaseButton.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs index c64ad9bb..2c0478a8 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputBaseButton.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Base Card text input button class. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputComponent.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputComponent.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs index b8239e5e..7b316f8f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputComponent.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension text input component. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputIconButton.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs similarity index 88% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputIconButton.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs index 68e480ed..03841582 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputIconButton.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Card text input button with icon. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputTitleButton.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs similarity index 88% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputTitleButton.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs index 1038ad8a..8f9d5339 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardTextInputTitleButton.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Card text input button with text. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardViewParameters.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardViewParameters.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs index 5126d5ba..e9438c6c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/CardViewParameters.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Adaptive Card Extension Card View Parameters. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/ICardButtonBase.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs similarity index 85% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/ICardButtonBase.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs index 16249da5..1242d90f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardView/ICardButtonBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; -namespace Microsoft.Agents.Core.SharePoint.Models.CardView +namespace Microsoft.Agents.SharePoint.Models.CardView { /// /// Base properties for the buttons used in Adaptive Card Extensions card view components. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewHandleActionResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewHandleActionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs index e9b25971..d45f2bdc 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewHandleActionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Adaptive Card Extension Client-side action response to render card view. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs similarity index 89% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs index 0ecacdb4..888fe545 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/CardViewResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; -using Microsoft.Agents.Core.SharePoint.Models.CardView; +using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.CardView; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint Card View Data object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/GetPropertyPaneConfigurationResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/GetPropertyPaneConfigurationResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs index c53cccfe..87b52bdd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/GetPropertyPaneConfigurationResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint GetPropertyPaneConfiguration response object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneFieldProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs similarity index 89% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneFieldProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs index 7d6ac99a..e089bdca 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneFieldProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Interface for property pane field properties. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneGroupOrConditionalGroup.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs similarity index 89% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneGroupOrConditionalGroup.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs index e5ae93c0..0381a888 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/IPropertyPaneGroupOrConditionalGroup.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Interface for property pane group or conditional group. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/NoOpHandleActionResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/NoOpHandleActionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs index 1673d0ca..591fd5cb 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/NoOpHandleActionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Adaptive Card Extension Client-side action no-op response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneCheckboxProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneCheckboxProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs index e799230e..e3720d83 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneCheckboxProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane checkbox properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupIconProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupIconProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs index 5308ded5..97493eae 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupIconProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane choice group icon properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupImageSize.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupImageSize.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs index 4759a1b4..f89fa01e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupImageSize.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane choice group image size object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupOption.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupOption.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs index 8ce131c8..c709841c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupOption.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane choice group option object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs index adba5e52..8b5d7cea 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneChoiceGroupProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane choice group properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownOption.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownOption.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs index 7ac0272a..14dced25 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownOption.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane drop down option object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs index ea0cea89..1a88ea4a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneDropDownProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane drop down properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroup.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroup.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs index 6d6334e6..1b665f2f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroup.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane group object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroupField.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroupField.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs index f5903141..66a2dc5e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneGroupField.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane group field object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLabelProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLabelProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs index 63c672d9..00425661 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLabelProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane label properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkPopupWindowProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkPopupWindowProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs index 0f97d50f..8b6b7a8e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkPopupWindowProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane link popup window properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs index bdcf14fc..a5dee509 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneLinkProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane link properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePage.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePage.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs index b24202d2..b04e7e9b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePage.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane page object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePageHeader.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePageHeader.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs index 764b8471..f942c150 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPanePageHeader.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane page header object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneSliderProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneSliderProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs index fff602e0..f4e4c13d 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneSliderProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane slider properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneTextFieldProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneTextFieldProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs index b956ed60..77dcc9dd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneTextFieldProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane text field properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneToggleProperties.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneToggleProperties.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs index b294c680..48844949 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/PropertyPaneToggleProperties.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint property pane toggle properties object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewData.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewData.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs index 1e291678..6531160c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewData.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint Quick View Data object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewHandleActionResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewHandleActionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs index aaa176ff..bdb6fdc6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewHandleActionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// Adaptive Card Extension Client-side action response to render quick view. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewResponse.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewResponse.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs index eb616510..805a1e65 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Models/QuickViewResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.SharePoint.Models.Actions; +using Microsoft.Agents.SharePoint.Models.Actions; -namespace Microsoft.Agents.Core.SharePoint.Models +namespace Microsoft.Agents.SharePoint.Models { /// /// SharePoint GetQuickView response object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/README.md b/src/libraries/Partner/Microsoft.Agents.SharePoint/README.md new file mode 100644 index 00000000..acfc8d0b --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/README.md @@ -0,0 +1,5 @@ +# Microsoft.Agents.SharePoint + +## About + +Contains the definitions for SharePoint. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceDataConverter.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs similarity index 86% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceDataConverter.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs index 82e4beaa..c722074b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceDataConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.SharePoint.Models; +using Microsoft.Agents.SharePoint.Models; using System.Text.Json; -namespace Microsoft.Agents.Core.SharePoint.Serialization.Converters +namespace Microsoft.Agents.SharePoint.Serialization.Converters { internal class AceDataConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceRequestConverter.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs similarity index 86% rename from src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceRequestConverter.cs rename to src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs index 93b2036f..2660d397 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Sharepoint/Serialization/Converters/AceRequestConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.SharePoint.Models; +using Microsoft.Agents.SharePoint.Models; using System.Text.Json; -namespace Microsoft.Agents.Core.SharePoint.Serialization.Converters +namespace Microsoft.Agents.SharePoint.Serialization.Converters { internal class AceRequestConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs new file mode 100644 index 00000000..4049d36b --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.SharePoint.Serialization.Converters; +using System.Text.Json; + +namespace Microsoft.Agents.SharePoint.Serialization +{ + public static class SerializerExtensions + { + public static JsonSerializerOptions ApplySharePointOptions(this JsonSerializerOptions options) + { + options.Converters.Add(new AceDataConverter()); + options.Converters.Add(new AceRequestConverter()); + + return options; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs similarity index 84% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs index 210a5c7b..90c205cd 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConfigHandlerAsync.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application { /// /// Function for handling config events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs index 9d477822..8360c429 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopData.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs @@ -1,5 +1,5 @@  -namespace Microsoft.Teams.AI.Application +namespace Microsoft.Agents.Teams.Application { /// /// Data returned when the thumbsup or thumbsdown button is clicked and response is received. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs similarity index 87% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs index c7ddd9d4..dcc6a2e0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FeedbackLoopHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs @@ -1,9 +1,9 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI.Application +namespace Microsoft.Agents.Teams.Application { /// /// Function for feedback loop activites diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs similarity index 85% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs index a96b7f68..2efeb5e4 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/FileConsentCardHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application { /// /// Function for handling file consent card activities. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs new file mode 100644 index 00000000..ca4a555d --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs @@ -0,0 +1,25 @@ + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.Teams.Application +{ + /// + /// A plugin responsible for downloading files relative to the current user's input. + /// + /// Type of application state. + public interface IInputFileDownloader where TState : TurnState, new() + { + /// + /// Download any files relative to the current user's input. + /// + /// The turn context. + /// The turn state. + /// The cancellation token. + /// A list of input files + public Task> DownloadFilesAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken = default); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs index 1fcee77d..50ce83d0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/InputFile.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs @@ -1,7 +1,7 @@  using System; -namespace Microsoft.Teams.AI.Application +namespace Microsoft.Agents.Teams.Application { /// /// Represents an upload file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs similarity index 85% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs index f8f399bd..91ca719a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/Meetings.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs @@ -1,11 +1,13 @@ -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; -using Microsoft.Teams.AI.Utilities; +using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Models; using System; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.Meetings { /// /// Meetings class to enable fluent style registration of handlers related to Microsoft Teams Meetings. @@ -41,7 +43,7 @@ public Application OnStart(MeetingStartHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingStartEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + MeetingStartEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -64,7 +66,7 @@ public Application OnEnd(MeetingEndHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingEndEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + MeetingEndEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -87,7 +89,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingParticipantsEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -110,7 +112,7 @@ public Application OnParticipantsLeave(MeetingParticipantsEventHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingParticipantsEventDetails meeting = ActivityUtilities.GetTypedValue(turnContext.Activity) ?? new(); + MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs similarity index 93% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs index 3f5c2f5c..deb4f668 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Meetings/MeetingsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.Meetings { /// /// Function for handling Microsoft Teams meeting start events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs index c2302b1f..c34b0a7f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs @@ -1,17 +1,20 @@ using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder.Application.Exceptions; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.Exceptions; -using Microsoft.Teams.AI.State; -using Microsoft.Teams.AI.Utilities; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Models; using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.MessageExtensions { /// /// Constants for message extension invoke names @@ -101,7 +104,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) - || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { throw new TeamsAIException($"Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: {turnContext.Activity.Type}"); } @@ -197,7 +200,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec MessagingExtensionAction? messagingExtensionAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) - || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "edit")) { throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: {turnContext.Activity.Type}"); @@ -297,7 +300,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec MessagingExtensionAction? messagingExtensionAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) - || (messagingExtensionAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null + || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "send")) { throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: {turnContext.Activity.Type}"); @@ -489,7 +492,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle MessagingExtensionQuery? messagingExtensionQuery; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.QUERY_INVOKE_NAME) - || (messagingExtensionQuery = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + || (messagingExtensionQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { throw new TeamsAIException($"Unexpected MessageExtensions.OnQuery() triggered for activity type: {turnContext.Activity.Type}"); } @@ -605,7 +608,7 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) }; RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { - AppBasedLinkQuery? appBasedLinkQuery = ActivityUtilities.GetTypedValue(turnContext.Activity); + AppBasedLinkQuery? appBasedLinkQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added @@ -643,7 +646,7 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync ha }; RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { - AppBasedLinkQuery? appBasedLinkQuery = ActivityUtilities.GetTypedValue(turnContext.Activity); + AppBasedLinkQuery? appBasedLinkQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs index 8d9de6d9..78bcdbf1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MessageExtensions/MessageExtensionsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs @@ -1,12 +1,13 @@ -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.MessageExtensions { /// /// Function for handling Message Extension submitAction events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs similarity index 85% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs index 8e43a564..9a7bdf27 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/O365ConnectorCardActionHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application { /// /// Function for handling O365 Connector Card Action activities. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs similarity index 83% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs index ee3e807c..f71ab9b2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ReadReceiptHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application { /// /// Function for handling read receipt events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs similarity index 91% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs index 5100d704..10200e1f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModules.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs @@ -1,16 +1,18 @@ using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.Application.Exceptions; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.Exceptions; -using Microsoft.Teams.AI.State; -using Microsoft.Teams.AI.Utilities; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Models; using System; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.TaskModules { /// /// TaskModules class to enable fluent style registration of handlers related to Task Modules. @@ -21,7 +23,9 @@ public class TaskModules { private static readonly string FETCH_INVOKE_NAME = "task/fetch"; private static readonly string SUBMIT_INVOKE_NAME = "task/submit"; - private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; + + //TODO + //private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; private readonly Application _app; @@ -42,11 +46,16 @@ public TaskModules(Application app) /// The application instance for chaining purposes. public Application OnFetch(string verb, FetchHandlerAsync handler) { + throw new NotImplementedException(); + + //TODO + /* Verify.ParamNotNull(verb); Verify.ParamNotNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); + */ } /// @@ -57,11 +66,16 @@ public Application OnFetch(string verb, FetchHandlerAsync handle /// The application instance for chaining purposes. public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) { + throw new NotImplementedException(); + + //TODO + /* Verify.ParamNotNull(verbPattern); Verify.ParamNotNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); + */ } /// @@ -79,7 +93,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle TaskModuleAction? taskModuleAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, FETCH_INVOKE_NAME) - || (taskModuleAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { throw new TeamsAIException($"Unexpected TaskModules.OnFetch() triggered for activity type: {turnContext.Activity.Type}"); } @@ -142,11 +156,16 @@ public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHa /// The application instance for chaining purposes. public Application OnSubmit(string verb, SubmitHandlerAsync handler) { + throw new NotImplementedException(); + + //TODO + /* Verify.ParamNotNull(verb); Verify.ParamNotNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); + */ } @@ -158,11 +177,16 @@ public Application OnSubmit(string verb, SubmitHandlerAsync hand /// The application instance for chaining purposes. public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) { + throw new NotImplementedException(); + + //TODO + /* Verify.ParamNotNull(verbPattern); Verify.ParamNotNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); + */ } /// @@ -180,7 +204,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand TaskModuleAction? taskModuleAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_INVOKE_NAME) - || (taskModuleAction = ActivityUtilities.GetTypedValue(turnContext.Activity)) == null) + || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity)) == null) { throw new TeamsAIException($"Unexpected TaskModules.OnSubmit() triggered for activity type: {turnContext.Activity.Type}"); } @@ -265,7 +289,7 @@ private static RouteSelectorAsync CreateTaskSelector(Func isMatch, return Task.FromResult(isVerbMatch); */ - return Task.FromResult(false); + return Task.FromResult(false); }; return routeSelector; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs similarity index 90% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs index 2b67ebbf..e4a609c1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs @@ -1,10 +1,10 @@ -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Teams.Models; -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.TaskModules { /// /// Function for handling Task Module fetch events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs similarity index 90% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs index 2b0cc3fc..72af0397 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TaskModules/TaskModulesOptions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Teams.AI +namespace Microsoft.Agents.Teams.Application.TaskModules { /// /// Options for TaskModules class. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs new file mode 100644 index 00000000..71114e98 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs @@ -0,0 +1,395 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Models; +using System; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.Application.Route; +using Microsoft.Agents.Teams.Application.Meetings; +using Microsoft.Agents.Teams.Application.MessageExtensions; +using Microsoft.Agents.Teams.Application.TaskModules; + +namespace Microsoft.Agents.Teams.Application +{ + /// + /// Application class for routing and processing incoming requests. + /// + /// Type of the turnState. This allows for strongly typed access to the turn turnState. + public class TeamsApplication : Application + where TState : TurnState, new() + { + private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; + private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; + + //TODO: Teams Application isn't handling: + // InputFiles to TurnState.Temp (BeforeTurn now?) + // AI.run + + /// + /// Creates a new Application instance. + /// + /// Optional. Options used to configure the application. + /// + public TeamsApplication(ApplicationOptions options) : base(options) + { + Meetings = new Meetings(this); + MessageExtensions = new MessageExtensions(this); + TaskModules = new TaskModules(this); + } + + /// + /// Fluent interface for accessing Meetings' specific features. + /// + public Meetings Meetings { get; } + + /// + /// Fluent interface for accessing Message Extensions' specific features. + /// + public MessageExtensions MessageExtensions { get; } + + /// + /// Fluent interface for accessing Task Modules' specific features. + /// + public TaskModules TaskModules { get; } + + /// + /// Handles conversation update events. + /// + /// Name of the conversation update event to handle, can use . + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public override Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + { + Verify.ParamNotNull(conversationUpdateEvent); + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector; + switch (conversationUpdateEvent) + { + case TeamsConversationUpdateEvents.ChannelCreated: + case TeamsConversationUpdateEvents.ChannelDeleted: + case TeamsConversationUpdateEvents.ChannelRenamed: + case TeamsConversationUpdateEvents.ChannelRestored: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + && context.Activity?.GetChannelData()?.Channel != null + && context.Activity?.GetChannelData()?.Team != null + ); + break; + } + case TeamsConversationUpdateEvents.MembersAdded: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersAdded != null + && context.Activity.MembersAdded.Count > 0 + ); + break; + } + case TeamsConversationUpdateEvents.MembersRemoved: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersRemoved != null + && context.Activity.MembersRemoved.Count > 0 + ); + break; + } + case TeamsConversationUpdateEvents.TeamRenamed: + case TeamsConversationUpdateEvents.TeamDeleted: + case TeamsConversationUpdateEvents.TeamHardDeleted: + case TeamsConversationUpdateEvents.TeamArchived: + case TeamsConversationUpdateEvents.TeamUnarchived: + case TeamsConversationUpdateEvents.TeamRestored: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + && context.Activity?.GetChannelData()?.Team != null + ); + break; + } + default: + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.GetChannelData()?.EventType, conversationUpdateEvent) + ); + break; + } + } + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message edit events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageEdit(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "editMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message undo soft delete events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageUndelete(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageUpdate, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "undeleteMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles message soft delete events. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnMessageDelete(RouteHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + TeamsChannelData teamsChannelData; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.MessageDelete, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams) + && (teamsChannelData = turnContext.Activity.GetChannelData()) != null + && string.Equals(teamsChannelData.EventType, "softDeleteMessage")); + }; + AddRoute(routeSelector, handler, isInvokeRoute: false); + return this; + } + + /// + /// Handles read receipt events for messages sent by the bot in personal scope. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnTeamsReadReceipt(ReadReceiptHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.ChannelId, Channels.Msteams) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.readReceipt") + ); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ReadReceiptInfo readReceiptInfo = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + await handler(turnContext, turnState, readReceiptInfo, cancellationToken); + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + return this; + } + + /// + /// Handles config fetch events for Microsoft Teams. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnConfigFetch(ConfigHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, CONFIG_FETCH_INVOKE_NAME) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles config submit events for Microsoft Teams. + /// + /// Function to call when the event is triggered. + /// The application instance for chaining purposes. + public Application OnConfigSubmit(ConfigHandlerAsync handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, CONFIG_SUBMIT_INVOKE_NAME) + && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); + RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + { + ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles when a file consent card is accepted by the user. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFileConsentAccept(FileConsentHandler handler) + => OnFileConsent(handler, "accept"); + + /// + /// Handles when a file consent card is declined by the user. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnFileConsentDecline(FileConsentHandler handler) + => OnFileConsent(handler, "decline"); + + private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => + { + FileConsentCardResponse? fileConsentCardResponse; + return Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "fileConsent/invoke") + && (fileConsentCardResponse = ProtocolJsonSerializer.ToObject(context.Activity!.Value)) != null + && string.Equals(fileConsentCardResponse.Action, fileConsentAction) + ); + }; + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + FileConsentCardResponse fileConsentCardResponse = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + await handler(turnContext, turnState, fileConsentCardResponse, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Handles O365 Connector Card Action activities. + /// + /// Function to call when the route is triggered. + /// The application instance for chaining purposes. + public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) + { + Verify.ParamNotNull(handler); + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "actionableMessage/executeAction") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + O365ConnectorCardActionQuery query = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + await handler(turnContext, turnState, query, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + + /// + /// Registers a handler for feedback loop events when a user clicks the thumbsup or thumbsdown button on a response sent from the AI module. + /// must be set to true. + /// + /// Function to cal lwhen the route is triggered + /// + public Application OnFeedbackLoop(FeedbackLoopHandler handler) + { + Verify.ParamNotNull(handler); + + RouteSelectorAsync routeSelector = (context, _) => + { + var jsonObject = ProtocolJsonSerializer.ToObject(context.Activity.Value); + string? actionName = jsonObject.ContainsKey("actionName") ? jsonObject["actionName"].ToString() : string.Empty; + return Task.FromResult + ( + context.Activity.Type == ActivityTypes.Invoke + && context.Activity.Name == "message/submitAction" + && actionName == "feedback" + ); + }; + + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + FeedbackLoopData feedbackLoopData = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)!; + feedbackLoopData.ReplyToId = turnContext.Activity.ReplyToId; + + await handler(turnContext, turnState, feedbackLoopData, cancellationToken); + + // Check to see if an invoke response has already been added + if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + { + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); + } + }; + + AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + return this; + } + } +} diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs new file mode 100644 index 00000000..1fbef7e3 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs @@ -0,0 +1,78 @@ +namespace Microsoft.Agents.Teams.Application +{ + /// + /// Conversation update events. + /// + public static class TeamsConversationUpdateEvents + { + /// + /// ChannelCreated event + /// + public const string ChannelCreated = "channelCreated"; + + /// + /// ChannelRenamed event + /// + public const string ChannelRenamed = "channelRenamed"; + + /// + /// ChannelDeleted event + /// + public const string ChannelDeleted = "channelDeleted"; + + /// + /// ChannelRestored event + /// + public const string ChannelRestored = "channelRestored"; + + /// + /// MembersAdded event + /// + public const string MembersAdded = "membersAdded"; + + /// + /// MembersRemoved event + /// + public const string MembersRemoved = "membersRemoved"; + + /// + /// TeamRenamed event + /// + public const string TeamRenamed = "teamRenamed"; + + /// + /// TeamDeleted event + /// + public const string TeamDeleted = "teamDeleted"; + + /// + /// TeamArchived event + /// + public const string TeamArchived = "teamArchived"; + + /// + /// TeamUnarchived event + /// + public const string TeamUnarchived = "teamUnarchived"; + + /// + /// TeamRestored event + /// + public const string TeamRestored = "teamRestored"; + + /// + /// TeamHardDeleted event + /// + public const string TeamHardDeleted = "teamHardDeleted"; + + /// + /// TopicName event + /// + public const string TopicName = "topicName"; + + /// + /// HistoryDisclosed event + /// + public const string HistoryDisclosed = "historyDisclosed"; + } +} diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/AttachmentExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/AttachmentExtensions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs index 2541bf9d..ddfe36a4 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/AttachmentExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; -namespace Microsoft.Agents.Core.Teams +namespace Microsoft.Agents.Teams { /// /// Attachment extensions. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs index b6cac71c..70b61407 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Linq; using System.Net; @@ -13,7 +15,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Teams +namespace Microsoft.Agents.Teams.Compat { /// /// The TeamsActivityHandler is derived from ActivityHandler. It adds support for diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsSSOTokenExchangeMiddleware.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs index 8502b40d..0a7f4a42 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs @@ -12,7 +12,7 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector; -namespace Microsoft.Agents.BotBuilder.Teams +namespace Microsoft.Agents.Teams.Compat { /// /// If the activity name is signin/tokenExchange, this middleware will attempt to diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsConnectorClient.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs similarity index 86% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsConnectorClient.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs index 01a00ad0..1f2082f5 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsConnectorClient.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Connector.Teams +using Microsoft.Agents.Connector; + +namespace Microsoft.Agents.Teams.Connector { /// /// The Connector for Microsoft Teams allows your bot to perform extended operations on a Microsoft Teams channel. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsOperations.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs similarity index 99% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsOperations.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs index 9a7d977e..38addb86 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ITeamsOperations.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs @@ -2,12 +2,12 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// TeamsOperations operations. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsConnectorClient.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs similarity index 92% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsConnectorClient.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs index c209eaae..cb623acb 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsConnectorClient.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Connector; using System; using System.Net; using System.Net.Http; using System.Threading.Tasks; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// TeamsConnectorClient REST implementation. This ConnectorClient is suitable for either ABS or SMBA. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsOperations.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs similarity index 99% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsOperations.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs index 6ac84840..5711b814 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RestTeamsOperations.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System; using System.Collections.Generic; using System.Net.Http; @@ -13,7 +13,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// TeamsOperations operations. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryAction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs similarity index 98% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryAction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs index 352b47d6..7f0920ae 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// Retries asynchronous operations. In case of errors, it collects and returns exceptions in an AggregateException object. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryParams.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs similarity index 98% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryParams.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs index 3f0e33c8..e7b2fd4c 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/RetryParams.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// Wrapper class that defines a retrying behavior. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs index b433f1c5..0cdc3ab1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs @@ -2,18 +2,17 @@ // Licensed under the MIT License. using Microsoft.Agents.Connector; -using Microsoft.Agents.Connector.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// The TeamsInfo Test If Build Remote Successful @@ -104,7 +103,7 @@ public static async Task> GetTeamChannelsAsync(ITurnContext t /// number of entries on the page. /// /// cancellation token. /// TeamsPagedMembersResult. - public static Task GetPagedTeamMembersAsync(ITurnContext turnContext, string teamId = null, string continuationToken = default(string), int? pageSize = default(int?), CancellationToken cancellationToken = default) + public static Task GetPagedTeamMembersAsync(ITurnContext turnContext, string teamId = null, string continuationToken = default, int? pageSize = default, CancellationToken cancellationToken = default) { var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team."); return GetPagedMembersAsync(GetConnectorClient(turnContext), t, continuationToken, cancellationToken, pageSize); @@ -118,7 +117,7 @@ public static async Task> GetTeamChannelsAsync(ITurnContext t /// ContinuationToken token. /// /// Cancellation token. /// TeamsPagedMembersResult. - public static Task GetPagedMembersAsync(ITurnContext turnContext, int? pageSize = default(int?), string continuationToken = default(string), CancellationToken cancellationToken = default) + public static Task GetPagedMembersAsync(ITurnContext turnContext, int? pageSize = default, string continuationToken = default, CancellationToken cancellationToken = default) { var teamInfo = turnContext.Activity.TeamsGetTeamInfo(); @@ -363,7 +362,7 @@ public static async Task SendMessageToAllUsersInTeamAsync(ITurnContext t return await teamsClient.Teams.SendMessageToAllUsersInTeamAsync(activity, teamId, tenantId, cancellationToken: cancellationToken).ConfigureAwait(false); } } - + /// /// Sends a message to the provided list of Teams channels. /// @@ -384,7 +383,7 @@ public static async Task SendMessageToListOfChannelsAsync(ITurnContext t return await teamsClient.Teams.SendMessageToListOfChannelsAsync(activity, channelsMembers, tenantId, cancellationToken: cancellationToken).ConfigureAwait(false); } } - + /// /// Gets the state of an operation. /// @@ -437,7 +436,7 @@ public static async Task CancelOperationAsync(ITurnContext turnContext, string o } } - private static async Task> GetMembersAsync(IConnectorClient connectorClient, string conversationId, CancellationToken cancellationToken) + private static async Task> GetMembersAsync(IConnectorClient connectorClient, string conversationId, CancellationToken cancellationToken) { if (conversationId == null) { @@ -471,7 +470,7 @@ private static async Task GetMemberAsync(IConnectorClient c return teamsChannelAccount; } - private static async Task GetPagedMembersAsync(IConnectorClient connectorClient, string conversationId, string continuationToken, CancellationToken cancellationToken, int? pageSize = default(int?)) + private static async Task GetPagedMembersAsync(IConnectorClient connectorClient, string conversationId, string continuationToken, CancellationToken cancellationToken, int? pageSize = default) { if (conversationId == null) { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ThrottleException.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs similarity index 97% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/ThrottleException.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs index 9e1ca998..10cf018f 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/ThrottleException.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// Custom throttling exception. diff --git a/src/libraries/Client/Microsoft.Agents.Connector/Teams/TimeSpanExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs similarity index 97% rename from src/libraries/Client/Microsoft.Agents.Connector/Teams/TimeSpanExtensions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs index 645b8c9d..7616e7d1 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/Teams/TimeSpanExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Connector.Teams +namespace Microsoft.Agents.Teams.Connector { /// /// Extension methods for the class. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj b/src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj new file mode 100644 index 00000000..b5156eb9 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj @@ -0,0 +1,38 @@ + + + + latest + CplTeams + true + README.md + + + + + Microsoft.Agents.Teams + Library for creating Teams agents using Microsoft Agent SDK + Library for building Teams agents using Microsoft Agents SDK + + + + annotations + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/AppBasedLinkQuery.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/AppBasedLinkQuery.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs index 527b4274..459bd806 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/AppBasedLinkQuery.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Invoke request body type for app-based link query. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntriesResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntriesResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs index c5c0c4dc..fb610f53 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntriesResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies the failed entries response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntry.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntry.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs index e8eeca12..44881ec5 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchFailedEntry.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies the failed entry with its id and error. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchOperationState.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchOperationState.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs index 17a0d163..357ce15c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BatchOperationState.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Object representing operation state. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BotConfigAuth.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BotConfigAuth.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs index cc932e7a..58dcb014 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/BotConfigAuth.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies bot config auth, including type and suggestedActions. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/CacheInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/CacheInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs index ecf9df0a..0e248f44 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/CacheInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// A cache info object which notifies Teams how long an object should be cached for. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ChannelInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ChannelInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs index a2240534..4f2a5c16 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ChannelInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// A channel info object which describes the channel. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigAuthResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs similarity index 90% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigAuthResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs index cf47a6f2..e2f12f0a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigAuthResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for Config Auth Response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs index 15a441f5..54bc4811 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for Config Response Payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponseBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponseBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs index 57894996..b0959633 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigResponseBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs @@ -3,7 +3,7 @@ -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies Invoke response base including response type. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigTaskResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs similarity index 91% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigTaskResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs index 7cbc5a92..0b065a99 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConfigTaskResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for Config Task Response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ContentType.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs similarity index 91% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ContentType.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs index 2cd1f275..14bfffba 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ContentType.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Defines content type. Depending on contentType, content field will have a different structure. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConversationList.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConversationList.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs index 990683ad..0db0a434 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ConversationList.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// List of channels under a team. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCard.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCard.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs index ac5e69b0..cd261095 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCard.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// File consent card attachment. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCardResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCardResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs index 6e2f0481..88ba2604 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileConsentCardResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents the value of the invoke activity sent when the user acts on diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileDownloadInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileDownloadInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs index 8f425ae4..d3dd1391 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileDownloadInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// File download info attachment. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileInfoCard.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileInfoCard.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs index cbae079f..a2f5f7d7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileInfoCard.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// File info card. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileUploadInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileUploadInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs index 73949b42..36250439 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/FileUploadInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Information about the file to be uploaded. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs index 3df53f90..1e46852f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetailsBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetailsBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs index 76812c27..e75ea1d6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingDetailsBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEndEventDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEndEventDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs index 1afb3b20..cf989853 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEndEventDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a Teams meeting end event. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEventDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEventDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs index 6ef1611c..e7c01303 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingEventDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs index 242944af..5fa740fa 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// General information about a Teams meeting. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotification.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotification.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs index 47f7c082..5b29fc08 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotification.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies Bot meeting notification including meeting notification value. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs index 1f318e63..f4b2de28 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies Bot meeting notification base including channel data and type. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationChannelData.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationChannelData.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs index 88b43151..825dc33b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationChannelData.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specify Teams Bot meeting notification channel data. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationRecipientFailureInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationRecipientFailureInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs index ed2a3fce..dd5d4d91 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationRecipientFailureInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Information regarding failure to notify a recipient of a meeting notification. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs index af2cabd2..7514f289 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingNotificationResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies Bot meeting notification response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs index 1d5a43dd..6d6d90e7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Teams meeting participant details. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantsEventDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantsEventDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs index 4040037d..054dfb5f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingParticipantsEventDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Data about the meeting participants. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStageSurface.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStageSurface.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs index 1c154011..a3a98293 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStageSurface.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies meeting stage surface. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStartEventDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStartEventDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs index f45a6239..a75d78e1 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingStartEventDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a Teams meeting start event. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingTabIconSurface.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingTabIconSurface.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs index 03ef6477..dd4c05ed 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MeetingTabIconSurface.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies meeting tab icon surface. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayload.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayload.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs index 4e154caa..2f3feefd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayload.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents the individual message within a chat or channel where a diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadApp.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadApp.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs index 2abf7fa6..2c620bf4 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadApp.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents an application entity. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadAttachment.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadAttachment.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs index 5dedb77b..5131efc5 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadAttachment.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents the attachment in a message. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadBody.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadBody.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs index 4ef47b22..e3cf198c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadBody.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Plaintext/HTML representation of the content of the message. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadConversation.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadConversation.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs index 443b4b55..09658d89 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadConversation.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents a team or channel entity. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadFrom.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadFrom.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs index 916dac29..ffe99d8f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadFrom.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents a user, application, or conversation type that either sent diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadMention.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadMention.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs index 88a51acb..d1f52042 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadMention.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents the entity that was mentioned in the message. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadReaction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadReaction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs index ea019f4a..b898466e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadReaction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents the reaction of a user to a message. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadUser.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadUser.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs index 1104f3ab..773db4c9 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessageActionsPayloadUser.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents a user entity. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs index f9479a9d..ca1becdc 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionActionResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionActionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs index b726c827..54c50136 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionActionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Response of messaging extension action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAttachment.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAttachment.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs index 3109a32f..64b06bb3 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionAttachment.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension attachment. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionParameter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionParameter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs index 3b9f282e..44c2a05f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionParameter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension query parameters. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQuery.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQuery.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs index 840377b2..f29344f5 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQuery.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension query. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQueryOptions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQueryOptions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs index 6eab931a..83db1096 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionQueryOptions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension query options. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs index 2b8fe75d..a03af542 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResult.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResult.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs index 8bd4fbaa..7335dc86 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionResult.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension result. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionSuggestedAction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionSuggestedAction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs index 6fa7e4fa..57169a47 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/MessagingExtensionSuggestedAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Messaging extension Actions (Only when type is auth or config). diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/NotificationInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/NotificationInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs index 183d25b1..9859b62a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/NotificationInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies if a notification is to be sent for the mentions. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCard.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCard.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs index f9e00834..d626f37b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCard.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs index 18d54860..17356ec1 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card action base. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionCard.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionCard.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs index 6f0a52bf..5635ab53 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionCard.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card ActionCard action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionQuery.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionQuery.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs index 7614d7e0..1d877086 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardActionQuery.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card HttpPOST invoke query. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardDateInput.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardDateInput.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs index 0622f08b..5bd15d77 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardDateInput.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card date input. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardFact.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardFact.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs index 5ed5c9a8..5fe9e86d 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardFact.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card fact. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardHttpPOST.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardHttpPOST.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs index 591f19fc..4a69224e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardHttpPOST.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card HttpPOST action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardImage.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardImage.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs index 32f42267..20f2f15c 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardImage.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card image. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardInputBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardInputBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs index f56f2059..6db8011b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardInputBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card input for ActionCard action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInput.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInput.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs index 7ffc0b0d..28d3a2d9 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInput.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card multiple choice input. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs index 9676b413..02e6b24a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365O365 connector card multiple choice input item. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUri.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUri.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs index 6ca78cbf..b687917a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUri.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card OpenUri action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUriTarget.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUriTarget.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs index 5302a3aa..b6bd4f75 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardOpenUriTarget.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card OpenUri target. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardSection.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardSection.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs index 007dc5e3..ff791ae0 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardSection.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card section. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardTextInput.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardTextInput.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs index 2d3acbeb..0a6e5f70 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardTextInput.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card text input. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardViewAction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardViewAction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs index d6e2062b..8e8fc31f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/O365ConnectorCardViewAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// O365 connector card ViewAction action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/OnBehalfOf.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/OnBehalfOf.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs index 45b9e21f..53473ada 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/OnBehalfOf.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies attribution for notifications. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ReadReceiptInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ReadReceiptInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs index c621a589..97998cd2 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/ReadReceiptInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// General information about a read receipt. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SigninStateVerificationQuery.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SigninStateVerificationQuery.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs index 372b059b..a35d80e7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SigninStateVerificationQuery.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Signin state (part of signin action auth flow) verification invoke query. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/Surface.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/Surface.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs index 0d71009a..4d4537a7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/Surface.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies where the notification will be rendered in the meeting UX. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SurfaceType.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs similarity index 92% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SurfaceType.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs index c5579449..7feb977b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/SurfaceType.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Defines Teams Surface type for use with a object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabContext.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabContext.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs index f17c96de..1de9d66f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabContext.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Current tab request context, i.e., the current theme. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabEntityContext.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabEntityContext.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs index 0faf311e..55596e83 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabEntityContext.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Current TabRequest entity context, or 'tabEntityId'. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabRequest.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabRequest.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs index 835be6f5..403e2800 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabRequest.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Invoke ('tab/fetch') request value payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs index dcabf7e9..8a6b7fdd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for Card Tab Response Payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCard.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCard.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs index 130c784a..1d553daa 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCard.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for cards for a Tab request. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCards.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCards.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs index a18f0f07..14972198 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponseCards.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for cards for a . diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponsePayload.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponsePayload.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs index e6aa334b..3402b75b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabResponsePayload.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Payload for Tab Response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmit.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmit.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs index 8d3bc598..92cc7727 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmit.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Invoke ('tab/submit') request value payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmitData.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmitData.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs index eab5ee98..99549868 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSubmitData.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Invoke ('tab/submit') request value payload data. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSuggestedActions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSuggestedActions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs index df6fb0b3..659787f9 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TabSuggestedActions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Tab SuggestedActions (Only when type is 'auth' or 'silentAuth'). diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotification.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotification.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs index e1df2fcb..562d5e0b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotification.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies Teams targeted meeting notification. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotificationValue.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotificationValue.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs index 6190eca5..5e011f5f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TargetedMeetingNotificationValue.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specifies the targeted meeting notification value, including recipients and surfaces. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleAction.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleAction.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs index 82a462cd..5c64840b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleAction.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs @@ -7,7 +7,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Adapter class to represent BotBuilder card action as adaptive card action (in type of Action.Submit). diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleCardResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleCardResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs index 6a3bd589..a7a8cdce 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleCardResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License.s -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Tab Response to 'task/submit' from a tab. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleContinueResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleContinueResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs index 75a6e422..0a22b01b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleContinueResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Task Module Response with continue action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleMessageResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleMessageResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs index 9e8fb4a8..ba566952 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleMessageResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Task Module response with message action. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequest.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequest.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs index 2f1022eb..7a586723 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequest.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Task module invoke request value payload. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequestContext.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequestContext.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs index 21c6524b..bc530aae 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleRequestContext.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Current user context, i.e., the current theme. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponse.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponse.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs index f2d7d78e..510e3bbe 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponse.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Envelope for Task Module Response. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponseBase.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponseBase.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs index d0eae34b..67b93039 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleResponseBase.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Base class for Task Module responses. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleTaskInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleTaskInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs index 0fb7dc96..7e3276c0 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TaskModuleTaskInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Metadata for a Task Module. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs index 3ed19b52..b8a56eab 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Details related to a team. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs index a04816d0..46f86e20 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Describes a team. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamMember.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamMember.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs index 77cb872e..e88c08e8 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamMember.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Describes a member. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelAccount.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelAccount.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs index 2c9dd60f..029d3033 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelAccount.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Teams channel account detailing user Azure Active Directory details. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelData.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelData.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs index 344153e8..d8b5a1e7 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelData.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Channel data specific to messages received in Microsoft Teams. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelDataSettings.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelDataSettings.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs index e19598d4..1174b23f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsChannelDataSettings.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Settings within teams channel data specific to messages received in Microsoft Teams. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs index 9637eaf6..964c83f6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Describes a Teams Meeting. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingMember.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingMember.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs index 5d9ccfe7..4120e129 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingMember.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Data about the meeting participants. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingParticipant.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingParticipant.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs index 8109c972..aa7f7c22 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsMeetingParticipant.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Teams meeting participant information, detailing user Azure Active Directory and meeting participant details. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsPagedMembersResult.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs similarity index 97% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsPagedMembersResult.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs index 43ff2d5c..b88fb7a6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsPagedMembersResult.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Represents a wrapper for a Teams members query result. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsParticipantChannelAccount.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs similarity index 98% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsParticipantChannelAccount.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs index 6f7e8385..124e603b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TeamsParticipantChannelAccount.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Teams participant channel account detailing user Azure Active Directory and meeting participant details. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TenantInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TenantInfo.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs index d9947a97..d1aa1590 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/TenantInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Describes a tenant. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/UserMeetingDetails.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Models/UserMeetingDetails.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs index cdd89456..299f964e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Models/UserMeetingDetails.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Core.Teams.Models +namespace Microsoft.Agents.Teams.Models { /// /// Specific details of a user in a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/README.md b/src/libraries/Partner/Microsoft.Agents.Teams/README.md new file mode 100644 index 00000000..e01d025b --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/README.md @@ -0,0 +1,5 @@ +# Microsoft.Agents.Teams + +## About + +Contains the definitions for Teams. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs similarity index 80% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs index cbceba9c..d113e838 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponseBase. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs similarity index 80% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs index 742e68bc..6c90c6ac 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponseBase. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/SurfaceConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/SurfaceConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs index 8e7b6ba8..18e51b8a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/SurfaceConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs @@ -2,14 +2,14 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System; using System.Collections.Generic; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { /// /// Converter which allows json to be expression to object or static object. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TabSubmitDataConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TabSubmitDataConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs index d9a09952..61849d45 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TabSubmitDataConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs @@ -6,9 +6,9 @@ using System.Text.Json; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TabSubmitDataConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs index ab9b7a5a..e62da839 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TaskModuleCardResponseConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs index 99cc6455..6e01ae86 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TaskModuleContinueResponseConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs index 930c1bb0..ce3884b6 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TaskModuleMessageResponseConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs index e46d482c..201eaa31 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TaskModuleResponseBaseConverter : ConnectorConverter { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs similarity index 94% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs index 88917a47..72606582 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TaskModuleResponseConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs @@ -2,12 +2,12 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponse. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TeamsChannelDataConverter.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TeamsChannelDataConverter.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs index 42351ccf..a4c22570 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Teams/Serialization/Converters/TeamsChannelDataConverter.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Core.Teams.Serialization.Converters +namespace Microsoft.Agents.Teams.Serialization.Converters { internal class TeamsChannelDataConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs new file mode 100644 index 00000000..6d24320a --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Teams.Serialization.Converters; +using System.Text.Json; + +namespace Microsoft.Agents.Teams.Serialization +{ + public static class SerializerExtensions + { + public static JsonSerializerOptions ApplyTeamsOptions(this JsonSerializerOptions options) + { + options.Converters.Add(new SurfaceConverter()); + options.Converters.Add(new TabSubmitDataConverter()); + options.Converters.Add(new TeamsChannelDataConverter()); + options.Converters.Add(new MessagingExtensionActionResponseConverter()); + options.Converters.Add(new TaskModuleResponseConverter()); + options.Converters.Add(new TaskModuleResponseBaseConverter()); + options.Converters.Add(new TaskModuleCardResponseConverter()); + options.Converters.Add(new TaskModuleContinueResponseConverter()); + options.Converters.Add(new TaskModuleMessageResponseConverter()); + options.Converters.Add(new MessagingExtensionAttachmentConverter()); + + return options; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs similarity index 98% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityExtensions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs index 91fcc648..63909e4e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Teams/TeamsActivityExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; -namespace Microsoft.Agents.BotBuilder.Teams +namespace Microsoft.Agents.Teams { /// /// The TeamsActivityExtensions diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs b/src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs new file mode 100644 index 00000000..9a977878 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Connector; +using Microsoft.Agents.Teams.Connector; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.Agents.Teams +{ + /// + /// A factory to create REST clients to interact with a Channel Service. + /// + /// + /// Connector and UserToken client factory. + /// + /// + /// + /// Thrown when an instance of is not found via . + public class TeamsChannelServiceClientFactory : IChannelServiceClientFactory + { + private readonly string _tokenServiceEndpoint; + private readonly string _tokenServiceAudience; + private readonly ILogger _logger; + private readonly IConnections _connections; + private readonly IHttpClientFactory _httpClientFactory; + + /// + /// Used to create an HttpClient with the fullname of this class + /// + /// + /// + /// + /// For testing purposes only. + public TeamsChannelServiceClientFactory( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + IConnections connections, + string tokenServiceEndpoint = AuthenticationConstants.BotFrameworkOAuthUrl, + string tokenServiceAudience = AuthenticationConstants.BotFrameworkScope, + ILogger logger = null) + { + ArgumentNullException.ThrowIfNull(configuration); + + _logger = logger ?? NullLogger.Instance; + _connections = connections ?? throw new ArgumentNullException(nameof(connections)); + _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + + var tokenEndpoint = configuration?.GetValue($"{nameof(TeamsChannelServiceClientFactory)}:TokenServiceEndpoint"); + _tokenServiceEndpoint = string.IsNullOrWhiteSpace(tokenEndpoint) + ? tokenServiceEndpoint ?? throw new ArgumentNullException(nameof(tokenServiceEndpoint)) + : tokenEndpoint; + + var tokenAudience = configuration?.GetValue($"{nameof(TeamsChannelServiceClientFactory)}:TokenServiceAudience"); + _tokenServiceAudience = string.IsNullOrWhiteSpace(tokenAudience) + ? tokenServiceAudience ?? throw new ArgumentNullException(nameof(tokenServiceAudience)) + : tokenAudience; + } + + /// + public Task CreateConnectorClientAsync(ClaimsIdentity claimsIdentity, string serviceUrl, string audience, CancellationToken cancellationToken, IList scopes = null, bool useAnonymous = false) + { + ArgumentException.ThrowIfNullOrWhiteSpace(serviceUrl); + ArgumentException.ThrowIfNullOrWhiteSpace(audience); + + // Intentionally create the TeamsConnectorClient since it supports the same operations as for ABS plus the Teams operations. + return Task.FromResult(new RestTeamsConnectorClient( + new Uri(serviceUrl), + _httpClientFactory, + useAnonymous ? null : () => + { + var tokenAccess = _connections.GetTokenProvider(claimsIdentity, serviceUrl) + ?? throw new InvalidOperationException($"An instance of IAccessTokenProvider not found for {BotClaims.GetAppId(claimsIdentity)}:{serviceUrl}"); + return tokenAccess.GetAccessTokenAsync(audience, scopes); + }, + typeof(TeamsChannelServiceClientFactory).FullName)); + } + + /// + public Task CreateUserTokenClientAsync(ClaimsIdentity claimsIdentity, CancellationToken cancellationToken, bool useAnonymous = false) + { + ArgumentNullException.ThrowIfNull(claimsIdentity); + + var appId = BotClaims.GetAppId(claimsIdentity) ?? Guid.Empty.ToString(); + + return Task.FromResult(new RestUserTokenClient( + appId, + new Uri(_tokenServiceEndpoint), + _httpClientFactory, + useAnonymous ? null : () => + { + var tokenAccess = _connections.GetTokenProvider(claimsIdentity, _tokenServiceEndpoint) + ?? throw new InvalidOperationException($"An instance of IAccessTokenProvider not found for {BotClaims.GetAppId(claimsIdentity)}:{_tokenServiceEndpoint}"); + return tokenAccess.GetAccessTokenAsync(_tokenServiceAudience, null); + }, + typeof(TeamsChannelServiceClientFactory).FullName, + _logger)); + } + } +} diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index 97bed479..92683f71 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -1,8 +1,8 @@ using EchoBot.Model; +using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Teams.AI; -using Microsoft.Teams.AI.State; using System.Threading; using System.Threading.Tasks; diff --git a/src/samples/Application/messaging.echoBot/Model/AppState.cs b/src/samples/Application/messaging.echoBot/Model/AppState.cs index 9df25f10..ba012510 100644 --- a/src/samples/Application/messaging.echoBot/Model/AppState.cs +++ b/src/samples/Application/messaging.echoBot/Model/AppState.cs @@ -1,4 +1,4 @@ -using Microsoft.Teams.AI.State; +using Microsoft.Agents.BotBuilder.Application.State; using System; namespace EchoBot.Model diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs index 79b557a8..780a447a 100644 --- a/src/samples/Application/messaging.echoBot/Program.cs +++ b/src/samples/Application/messaging.echoBot/Program.cs @@ -2,7 +2,7 @@ using EchoBot.Model; using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Models; +using Microsoft.Agents.BotBuilder.Application; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Teams.AI; var builder = WebApplication.CreateBuilder(args); @@ -52,7 +51,7 @@ if (app.Environment.IsDevelopment()) { - app.MapGet("/", () => "Microsoft Copilot SDK Sample"); + app.MapGet("/", () => "Microsoft Agents SDK Sample"); app.UseDeveloperExceptionPage(); app.MapControllers().AllowAnonymous(); } diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs index 88e1542f..24ac1b3e 100644 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.State; diff --git a/src/samples/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/BotToBot/Bot1/Bots/Bot1.cs index 853a4d24..b40cc492 100644 --- a/src/samples/BotToBot/Bot1/Bots/Bot1.cs +++ b/src/samples/BotToBot/Bot1/Bots/Bot1.cs @@ -17,6 +17,7 @@ using Microsoft.Agents.Core; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector.Types; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.Samples.Bots { diff --git a/src/samples/BotToBot/Bot2/Bots/Bot2.cs b/src/samples/BotToBot/Bot2/Bots/Bot2.cs index 773e9515..c2f3f654 100644 --- a/src/samples/BotToBot/Bot2/Bots/Bot2.cs +++ b/src/samples/BotToBot/Bot2/Bots/Bot2.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs b/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs index ae8f3efe..40ee3a11 100644 --- a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs +++ b/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; diff --git a/src/samples/EchoBot/MyBot.cs b/src/samples/EchoBot/MyBot.cs index 24419768..1618ab7e 100644 --- a/src/samples/EchoBot/MyBot.cs +++ b/src/samples/EchoBot/MyBot.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; diff --git a/src/samples/SemanticKernel/WeatherBot/MyBot.cs b/src/samples/SemanticKernel/WeatherBot/MyBot.cs index 8fdeb06e..f4b52f43 100644 --- a/src/samples/SemanticKernel/WeatherBot/MyBot.cs +++ b/src/samples/SemanticKernel/WeatherBot/MyBot.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs index 25f5ecc0..a3e92736 100644 --- a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs +++ b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs @@ -11,7 +11,7 @@ using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using AdaptiveCards.Templating; using Microsoft.Agents.Core.Serialization; diff --git a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs index 106eea4b..eb2612af 100644 --- a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs +++ b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs @@ -9,7 +9,7 @@ using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; namespace LinkUnfurling.Bots { diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs index 59ea750f..fed067d4 100644 --- a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs +++ b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs @@ -15,7 +15,7 @@ using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs index fa3b2bed..c4b44ea7 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using System.Net.Http; using System.Text.Json; diff --git a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs index 75297313..7cd8f5df 100644 --- a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs +++ b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs @@ -9,7 +9,7 @@ using AdaptiveCards; using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using System.Text.Json.Nodes; using Microsoft.Agents.Core.Interfaces; diff --git a/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs b/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs index fe016032..2f5ac7b8 100644 --- a/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs +++ b/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; namespace TaskModule.Models { diff --git a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs index 5a2f58aa..acf5f6d1 100644 --- a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.State; using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; namespace BotAllCards.Bots { diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs index 00acee50..83c43acb 100644 --- a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs +++ b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs @@ -10,7 +10,7 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 801a3fc5..b85a6c7c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -19,6 +19,7 @@ using Microsoft.Agents.Telemetry; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs index de8f05e3..b365188a 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs @@ -8,6 +8,7 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Bots { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs index 4627689d..978e7db9 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs index 3956de01..ec0980b3 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Bots { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Debugging/DebugBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Debugging/DebugBot.cs index 02bf3224..c6184bcb 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Debugging/DebugBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Debugging/DebugBot.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Compat; + namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Debugging { public class DebugBot : ActivityHandler diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs index aa2a2ccf..f2aa430a 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs index 148f6fce..89c2efa5 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs @@ -11,7 +11,7 @@ using Moq; using System.Threading; using System.Net.Http; -using Microsoft.Agents.Connector.Teams; +using Microsoft.Agents.Teams.Connector; using Microsoft.Agents.Connector; namespace Microsoft.Agents.BotBuilder.Tests diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs index d42c05c9..d8060aeb 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs @@ -10,7 +10,7 @@ using Xunit; using Microsoft.Agents.BotBuilder.SharePoint; using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.SharePoint.Models; +using Microsoft.Agents.SharePoint.Models; namespace Microsoft.Agents.BotBuilder.Tests.SharePoint { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs index d3d75af6..3b66d583 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs @@ -6,7 +6,7 @@ using System.Text.Json; using System.Threading.Tasks; using Xunit; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System; using System.Globalization; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs index 7ad6c334..bfacf493 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Reflection; using System.Text.Json; diff --git a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs index a21608b9..af7c9d8b 100644 --- a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs @@ -22,6 +22,7 @@ using Moq; using Xunit; using Xunit.Abstractions; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.Client.Tests { diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs index 3d932475..f541384f 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector.Teams; +using Microsoft.Agents.Teams.Connector; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Moq; using System; using System.Collections.Generic; diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs index b0e021fd..45fa148a 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector.Teams; +using Microsoft.Agents.Teams.Connector; using System; using System.Threading.Tasks; using Xunit; diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs index 5c059c2f..186ea7a0 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector.Teams; +using Microsoft.Agents.Teams.Connector; using System; using Xunit; diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs index 25001aaa..89617548 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs index 1a954df9..9e37b706 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Client; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs index bd3a9af9..2622743c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs index e94adae6..16856ff5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Teams; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs index a0af24f4..0d05d0ff 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs index 82a90b9a..a0537bcd 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs index bd65fd1e..c3006771 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs index 5bad0b12..dab3d185 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs index 1586d116..06b83002 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs index 508411d7..702ba44d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs index 218e9d83..dbedc8e9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs index 70b16511..4218c2dd 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs index fe8ca1a9..0287fe60 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs index d91795c2..3750f640 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs index 48f11d7b..94ae8a42 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs index b5496949..034431c7 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs index 3c2dae0f..0334802d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs index c7375c53..1bf7ee0c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs index 20115572..d9e7a589 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs index 2204912b..a6a75faf 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs index 8bdf9ca7..99730621 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs index 58927df8..67efa031 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs index 5f2c7f4c..d718788d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs index 8ae86dc8..3d3a9ce6 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs index 085f634a..f58d4992 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs index 24422aa4..42b45108 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Text.Json; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs index 2af1fa9e..fe11e46b 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs index c613079e..5f2fed01 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs index 99b6ee3b..e3d6ca43 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs index e27a9583..b53723fe 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs index 8928d548..f98fe533 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs index e8a59372..92b7011e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs index 17de9528..4bbf2d3e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs index 90490e32..3b0738eb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs index 83c1f0ea..804955cb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs index f15145ff..686b81c3 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs index 68fa43a5..cf8ed693 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs index ce7b7515..30cc7383 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs index ee929af3..be51d42d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs index 28dfd263..726f660f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs index b04f10b6..f595ba05 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs index a3a28e3b..3f7fc859 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs @@ -1,7 +1,7 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs index 21aabc87..c9f14dae 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs index 405debc7..454757ba 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs @@ -1,7 +1,7 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs index b8e138d5..6aaaece3 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs index 24f749bd..bd075c28 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs index 9704f861..0819f8b5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs index 91c93515..e68095ba 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs index 38222953..ce9fe1b1 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs index 134ae772..9710cb98 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs index a9409401..12e5d4f1 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs index b37f1e49..0c74abde 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs @@ -1,7 +1,7 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs index 04b9c8ab..2638ea8c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs index 05b88480..228ef45b 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs index f6c5f318..68386a41 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs index b09e691f..02ab6c40 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs index 872ac239..017f4da5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs index 79d19e64..c6fd4d6c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs index d2f57234..63f7abc6 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs index 24c1798c..9122f5b2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs index 2160a446..30ea65dd 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs index da994cbf..e91a5ef6 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs index 14f4c146..5d391b15 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Collections; using System.Collections.Generic; using System.Text.Json; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs index adb6bb89..71ef4700 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs index ad05e2ff..30b7acee 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; using static Microsoft.Agents.Teams.Tests.TabsTestData; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs b/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs index c78b0be4..12c8cbb9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs @@ -6,7 +6,7 @@ using System.Text.Json; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; namespace Microsoft.Agents.Teams.Tests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs index 8d6a964e..5b10022e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using System.Text.Json; using Xunit; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs index 4c97c06d..52dce2d1 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs index ceb6e247..4046c8ef 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs index 4d510556..8cad1dcd 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs index f41edba4..3104d782 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs index 446b6a85..ef27ddb7 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs index 98d1a8b2..d925a79f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs index 33830c3a..6681b2f2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs index 74e3398e..be18a489 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs index ba7d64f8..3b380647 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs index cd4d7054..c5a60258 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs index 862a3a65..87ae54da 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs index 2443637e..1ae0c651 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Core; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs index 7f87a96f..98542890 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs index bd2f8bd1..f17132a1 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs index 0ab86c47..66752b40 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs index 05467b89..042e878a 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs index eb0f3715..2711d476 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Teams.Models; +using Microsoft.Agents.Teams.Models; using Xunit; namespace Microsoft.Agents.Teams.Tests From ea31626ea08f4346272f1c16f39b4acc072717f1 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 31 Jan 2025 09:27:14 -0600 Subject: [PATCH 07/60] Allow AddBot to specificy client factory --- .../TelemetryLoggerMiddleware.cs | 5 +++-- .../AspNetCore/ServiceCollectionExtensions.cs | 12 ++++++++++-- .../BotConversationSsoQuickstart.csproj | 1 + .../Bots/DialogBot.cs | 2 +- .../Teams/bot-conversation-sso-quickstart/Program.cs | 10 ++++++++-- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs b/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs index 5903b92e..96a58cf3 100644 --- a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs +++ b/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; @@ -380,6 +378,8 @@ protected Task> FillDeleteEventPropertiesAsync(IActiv private static void PopulateAdditionalChannelProperties(IActivity activity, Dictionary properties) { + // TODO + /* switch (activity.ChannelId) { case Channels.Msteams: @@ -395,6 +395,7 @@ private static void PopulateAdditionalChannelProperties(IActivity activity, Dict break; } + */ } } } diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 4b62b597..723a9de2 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -45,18 +45,26 @@ public static void AddCloudAdapter(this IServiceCollection services) where T public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) where TBot : class, IBot { - return AddBot(builder); + return AddBot(builder); } public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) where TBot : class, IBot where TAdapter : CloudAdapter + { + return AddBot(builder); + } + + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) + where TBot : class, IBot + where TAdapter : CloudAdapter + where TClientFactory : class, IChannelServiceClientFactory { // Add Connections object to access configured token connections. builder.Services.AddSingleton(); // Add factory for ConnectorClient and UserTokenClient creation - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Add the ChannelAdapter, this is the default adapter that works with Azure Bot Service and Activity Protocol. AddCloudAdapter(builder.Services); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj b/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj index 9592f042..adea441a 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj +++ b/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj @@ -15,6 +15,7 @@ + diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs index 43d5ca93..42241464 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs @@ -7,9 +7,9 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Extensions.Logging; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace BotConversationSsoQuickstart.Bots { diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index b7c0f422..595c6627 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -12,8 +12,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Teams; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Serialization; var builder = WebApplication.CreateBuilder(args); @@ -26,8 +29,11 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); +// Add Teams serialization support +ProtocolJsonSerializer.SerializationOptions.ApplyTeamsOptions(); + // Add basic bot functionality -builder.AddBot, TeamsSSOAdapter>(); +builder.AddBot, TeamsSSOAdapter, TeamsChannelServiceClientFactory>(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); From 744c6add908ca889ab039ed60289330f4b5863e5 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 31 Jan 2025 09:35:56 -0600 Subject: [PATCH 08/60] Fixed sample startup (issue not in main) --- src/samples/Teams/bot-conversation-sso-quickstart/Program.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index 595c6627..c3bddabd 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -38,11 +38,14 @@ // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); +// Create the Conversation state. +builder.Services.AddSingleton(); + builder.Services.AddTransient((sp) => { return [ - new AutoSaveStateMiddleware(true, new ConversationState(sp.GetService())), + new AutoSaveStateMiddleware(true, sp.GetService()), new TeamsSSOTokenExchangeMiddleware(sp.GetService(), builder.Configuration["ConnectionName"]) ]; }); From d8d62868d58f14217b43bbb86319f48616702725 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 31 Jan 2025 11:48:07 -0600 Subject: [PATCH 09/60] Auto serialization init --- .../Serialization/ProtocolJsonSerializer.cs | 5 ++ .../Serialization/SerializationInit.cs | 46 +++++++++++++++++++ .../Serialization/SerializationInit.cs | 16 +++++++ .../Serialization/SerializerExtensions.cs | 2 +- .../Serialization/SerializationInit.cs | 16 +++++++ .../Serialization/SerializerExtensions.cs | 2 +- .../Program.cs | 5 -- 7 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs create mode 100644 src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs index ff8811e1..e141e2ca 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs @@ -18,6 +18,11 @@ public static class ProtocolJsonSerializer public const string ApplicationJson = "application/json"; public static JsonSerializerOptions SerializationOptions = CreateConnectorOptions(); + static ProtocolJsonSerializer() + { + SerializationInitAttribute.InitSerialization(); + } + public static JsonSerializerOptions CreateConnectorOptions() { var options = new JsonSerializerOptions() diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs new file mode 100644 index 00000000..37f574cb --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.Agents.Core.Serialization +{ + public class SerializationInitAttribute : Attribute + { + internal static void InitSerialization() + { + //init newly loaded assemblies + AppDomain.CurrentDomain.AssemblyLoad += (s, o) => InitAssembly(o.LoadedAssembly); + //and all the ones we currently have loaded + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + InitAssembly(assembly); + } + } + + private static void InitAssembly(Assembly assembly) + { + foreach (var type in GetLoadOnInitTypes(assembly)) + { + var init = type.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + if (init != null) + { + init.Invoke(assembly, null); + } + } + } + + private static IEnumerable GetLoadOnInitTypes(Assembly assembly) + { + foreach (Type type in assembly.GetTypes()) + { + if (type.GetCustomAttributes(typeof(SerializationInitAttribute), true).Length > 0) + { + yield return type; + } + } + } + } +} diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs new file mode 100644 index 00000000..e9a34362 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; + +namespace Microsoft.Agents.SharePoint.Serialization +{ + [SerializationInit] + internal class SerializationInit + { + public static void Init() + { + ProtocolJsonSerializer.SerializationOptions.ApplySharePointOptions(); + } + } +} diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs index 4049d36b..35cf5c23 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs @@ -6,7 +6,7 @@ namespace Microsoft.Agents.SharePoint.Serialization { - public static class SerializerExtensions + internal static class SerializerExtensions { public static JsonSerializerOptions ApplySharePointOptions(this JsonSerializerOptions options) { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs new file mode 100644 index 00000000..99cb4651 --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; + +namespace Microsoft.Agents.Teams.Serialization +{ + [SerializationInit] + internal class SerializationInit + { + public static void Init() + { + ProtocolJsonSerializer.SerializationOptions.ApplyTeamsOptions(); + } + } +} diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs index 6d24320a..2411e257 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs @@ -6,7 +6,7 @@ namespace Microsoft.Agents.Teams.Serialization { - public static class SerializerExtensions + internal static class SerializerExtensions { public static JsonSerializerOptions ApplyTeamsOptions(this JsonSerializerOptions options) { diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index c3bddabd..80151a0e 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -15,8 +15,6 @@ using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams; -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Serialization; var builder = WebApplication.CreateBuilder(args); @@ -29,9 +27,6 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add Teams serialization support -ProtocolJsonSerializer.SerializationOptions.ApplyTeamsOptions(); - // Add basic bot functionality builder.AddBot, TeamsSSOAdapter, TeamsChannelServiceClientFactory>(); From ca47a642cbe41cc3f9feb397add566778b59e04f Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 31 Jan 2025 12:45:10 -0600 Subject: [PATCH 10/60] Adjusted Teams samples for new package --- .../Compat/SharePointActivityHandler.cs | 3 +- .../SharePointSSOTokenExchangeMiddleware.cs | 1 + .../Microsoft.Agents.Teams/AssemblyInfo.cs | 8 ++ .../AuthenticationBot.csproj | 1 + src/samples/AuthenticationBot/Program.cs | 2 +- .../AdaptiveCardActions.csproj | 1 + .../Bots/AdaptiveCardActionsBot.cs | 2 +- .../Teams/AdaptiveCardActions/Program.cs | 3 +- .../Bots/TeamsConversationBot.cs | 3 +- .../ConversationBot/ConversationBot.csproj | 1 + src/samples/Teams/ConversationBot/Program.cs | 3 +- .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 2 +- .../Teams/LinkUnfurling/LinkUnfurling.csproj | 1 + src/samples/Teams/LinkUnfurling/Program.cs | 3 +- .../Bots/InMeetingNotifications.cs | 4 +- .../InMeetingNotificationsBot.csproj | 1 + .../Teams/Meetings-Notification/Program.cs | 3 +- .../Bots/TeamsMessagingExtensionsSearchBot.cs | 5 +- .../MessagingExtensionsSearch.csproj | 1 + .../MessagingExtensionsSearch/Program.cs | 3 +- .../TaskModule/Bots/TeamsTaskModuleBot.cs | 2 +- src/samples/Teams/TaskModule/Program.cs | 3 +- .../Teams/TaskModule/TaskModule.csproj | 1 + .../Bots/ActivityBot.cs | 2 +- .../PeoplePicker.csproj | 1 + .../Program.cs | 3 +- .../Bots/ActivityBot.cs | 2 +- .../Program.cs | 3 +- .../ReceiveMessagesWithRSC.csproj | 1 + .../Teams/bot-tag-mention/Bots/DialogBot.cs | 2 +- .../bot-tag-mention/Dialogs/MainDialog.cs | 5 +- src/samples/Teams/bot-tag-mention/Program.cs | 5 +- .../bot-tag-mention/TagMentionBot.csproj | 1 + .../Bots/DialogBot.cs | 2 +- .../Teams/bot-teams-authentication/Program.cs | 3 +- .../bot-teams-authentication/TeamsAuth.csproj | 1 + .../ChannelServiceClientFactoryTests.cs | 7 +- .../Microsoft.Agents.BotBuilder.Tests.csproj | 1 + .../NormalizeMentionsMiddlewareTests.cs | 1 + .../SharePointActivityHandlerTests.cs | 3 +- .../ShowTypingMiddlewareTests.cs | 1 + .../CloudAdapterTests.cs | 1 + .../AttachmentExtensionsTests.cs | 1 - .../Connector}/RestTeamsOperationsTests.cs | 2 +- .../Connector}/RetryActionTests.cs | 2 +- .../Connector}/RetryParamTests.cs | 2 +- .../Microsoft.Agents.Teams.Tests.csproj | 4 +- .../NotImplementedAdapter.cs | 17 ++++ .../SimpleAdapter.cs | 79 +++++++++++++++++++ .../TeamsActivityHandlerTests.cs | 3 +- .../TestActivityHandler.cs | 4 +- 51 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs rename src/tests/{Microsoft.Agents.Connector.Tests/Teams => Microsoft.Agents.Teams.Tests/Connector}/RestTeamsOperationsTests.cs (99%) rename src/tests/{Microsoft.Agents.Connector.Tests/Teams => Microsoft.Agents.Teams.Tests/Connector}/RetryActionTests.cs (98%) rename src/tests/{Microsoft.Agents.Connector.Tests/Teams => Microsoft.Agents.Teams.Tests/Connector}/RetryParamTests.cs (95%) create mode 100644 src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs create mode 100644 src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs rename src/tests/{Microsoft.Agents.BotBuilder.Tests/Teams => Microsoft.Agents.Teams.Tests}/TeamsActivityHandlerTests.cs (99%) rename src/tests/{Microsoft.Agents.BotBuilder.Tests/Teams => Microsoft.Agents.Teams.Tests}/TestActivityHandler.cs (99%) diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs index 70f82d55..3d53a940 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; @@ -10,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.SharePoint +namespace Microsoft.Agents.SharePoint.Compat { /// /// The SharePointActivityHandler is derived from ActivityHandler. It adds support for diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs index 7fc8c9b8..967ded77 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector; +using Microsoft.Agents.SharePoint.Compat; namespace Microsoft.Agents.SharePoint { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs new file mode 100644 index 00000000..19d78efc --- /dev/null +++ b/src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +// Allows us to access some internal methods from the Memory.Tests unit tests so we don't have to use reflection and we get compile checks. +[assembly: InternalsVisibleTo("Microsoft.Agents.Connector.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.Agents.Teams.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/samples/AuthenticationBot/AuthenticationBot.csproj b/src/samples/AuthenticationBot/AuthenticationBot.csproj index 3f8ec35f..edf4a6cd 100644 --- a/src/samples/AuthenticationBot/AuthenticationBot.csproj +++ b/src/samples/AuthenticationBot/AuthenticationBot.csproj @@ -9,6 +9,7 @@ + diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index 075a936a..dfbcfedd 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -10,8 +10,8 @@ using Microsoft.Agents.Hosting.AspNetCore; using AuthenticationBot; using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.State; +using Microsoft.Agents.Teams.Compat; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj b/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj index f9595276..35a9ecce 100644 --- a/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj +++ b/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj @@ -27,6 +27,7 @@ + diff --git a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs index 62620818..bd4811bf 100644 --- a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs +++ b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using AdaptiveCards.Templating; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace AdaptiveCardActions.Bots { diff --git a/src/samples/Teams/AdaptiveCardActions/Program.cs b/src/samples/Teams/AdaptiveCardActions/Program.cs index 5a360ded..73a3b56a 100644 --- a/src/samples/Teams/AdaptiveCardActions/Program.cs +++ b/src/samples/Teams/AdaptiveCardActions/Program.cs @@ -4,6 +4,7 @@ using AdaptiveCardActions.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -21,7 +22,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs index a3e92736..c3d7b902 100644 --- a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs +++ b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Models; @@ -17,6 +16,8 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector.Types; +using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Teams.Connector; namespace ConversationBot.Bots { diff --git a/src/samples/Teams/ConversationBot/ConversationBot.csproj b/src/samples/Teams/ConversationBot/ConversationBot.csproj index 63b9c454..7dc3a913 100644 --- a/src/samples/Teams/ConversationBot/ConversationBot.csproj +++ b/src/samples/Teams/ConversationBot/ConversationBot.csproj @@ -13,6 +13,7 @@ + diff --git a/src/samples/Teams/ConversationBot/Program.cs b/src/samples/Teams/ConversationBot/Program.cs index d5a167e7..cfcf32d1 100644 --- a/src/samples/Teams/ConversationBot/Program.cs +++ b/src/samples/Teams/ConversationBot/Program.cs @@ -4,6 +4,7 @@ using ConversationBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -17,7 +18,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs index eb2612af..a4d3d7ec 100644 --- a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs +++ b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs @@ -6,9 +6,9 @@ using System.Threading; using System.Threading.Tasks; using AdaptiveCards; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Models; namespace LinkUnfurling.Bots diff --git a/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj b/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj index 3f288322..7d17b9c9 100644 --- a/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj +++ b/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj @@ -13,6 +13,7 @@ + diff --git a/src/samples/Teams/LinkUnfurling/Program.cs b/src/samples/Teams/LinkUnfurling/Program.cs index 08f50f55..6a9126f2 100644 --- a/src/samples/Teams/LinkUnfurling/Program.cs +++ b/src/samples/Teams/LinkUnfurling/Program.cs @@ -4,6 +4,7 @@ using LinkUnfurling.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -17,7 +18,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs index fed067d4..918f4979 100644 --- a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs +++ b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs @@ -12,11 +12,13 @@ using System.Threading; using System.Threading.Tasks; using System.Web; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Teams; using Microsoft.Agents.Teams.Models; using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Teams.Connector; namespace InMeetingNotificationsBot.Bots diff --git a/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj b/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj index 76f77fed..bb90abac 100644 --- a/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj +++ b/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj @@ -15,6 +15,7 @@ + diff --git a/src/samples/Teams/Meetings-Notification/Program.cs b/src/samples/Teams/Meetings-Notification/Program.cs index 187977d7..c741dee2 100644 --- a/src/samples/Teams/Meetings-Notification/Program.cs +++ b/src/samples/Teams/Meetings-Notification/Program.cs @@ -4,6 +4,7 @@ using InMeetingNotificationsBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -19,7 +20,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); app.MapRazorPages(); diff --git a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs index c4b44ea7..22d9f43a 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; @@ -15,6 +14,7 @@ using System.Text.Json.Nodes; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace MessagingExtensionsSearch.Bots @@ -49,9 +49,6 @@ protected override async Task OnTeamsMessagingExtens var packages = await FindPackages(text); - // Provide a default icon URL - var defaultIconUrl = "https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/icon"; - // We take every row of the results and wrap them in cards wrapped in MessagingExtensionAttachment objects. // The Preview is optional, if it includes a Tap, that will trigger the OnTeamsMessagingExtensionSelectItemAsync event back on this bot. var attachments = packages.Select(package => diff --git a/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj b/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj index 28cb259c..205a6ae3 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj +++ b/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj @@ -13,6 +13,7 @@ + diff --git a/src/samples/Teams/MessagingExtensionsSearch/Program.cs b/src/samples/Teams/MessagingExtensionsSearch/Program.cs index 48423186..e971a659 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Program.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore; using MessagingExtensionsSearch.Bots; +using Microsoft.Agents.Teams; var builder = WebApplication.CreateBuilder(args); @@ -21,7 +22,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs index 7cd8f5df..9d8698f8 100644 --- a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs +++ b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs @@ -7,13 +7,13 @@ using System.Threading; using System.Threading.Tasks; using AdaptiveCards; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using System.Text.Json.Nodes; using Microsoft.Agents.Core.Interfaces; using TaskModule.Models; +using Microsoft.Agents.Teams.Compat; namespace TaskModule.Bots { diff --git a/src/samples/Teams/TaskModule/Program.cs b/src/samples/Teams/TaskModule/Program.cs index 717380e0..91434197 100644 --- a/src/samples/Teams/TaskModule/Program.cs +++ b/src/samples/Teams/TaskModule/Program.cs @@ -3,6 +3,7 @@ using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -20,7 +21,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/TaskModule/TaskModule.csproj b/src/samples/Teams/TaskModule/TaskModule.csproj index 44604e46..981861c1 100644 --- a/src/samples/Teams/TaskModule/TaskModule.csproj +++ b/src/samples/Teams/TaskModule/TaskModule.csproj @@ -8,6 +8,7 @@ + diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs index e8b7972d..eae71672 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs @@ -8,8 +8,8 @@ using System.Threading.Tasks; using AdaptiveCards.Templating; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace PeoplePicker.Bots diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj b/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj index 1f1dc387..6eb448e9 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj +++ b/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj @@ -14,6 +14,7 @@ + diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs index 5f40c9a1..f32fc200 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PeoplePicker.Bots; +using Microsoft.Agents.Teams; var builder = WebApplication.CreateBuilder(args); @@ -23,7 +24,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs index deeb5e99..96f8354e 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.BotBuilder.Teams; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace ReceiveMessagesWithRSC.Bots { diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs index 8af85cb0..5f49f965 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ReceiveMessagesWithRSC.Bots; +using Microsoft.Agents.Teams; var builder = WebApplication.CreateBuilder(args); @@ -23,7 +24,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj b/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj index 20283114..5e90ce53 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj @@ -9,6 +9,7 @@ + diff --git a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs index 052c0e5c..bd4cf4f2 100644 --- a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs @@ -6,9 +6,9 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Extensions.Logging; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace TagMentionBot.Bots { diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs index 83c43acb..25f8d273 100644 --- a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs +++ b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs @@ -8,8 +8,9 @@ using System.Threading.Tasks; using AdaptiveCards.Templating; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Teams; +using Microsoft.Agents.Teams.Connector; using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -119,7 +120,7 @@ await stepContext.Context.SendActivityAsync( client = new SimpleGraphClient(tokenResponse.Token); teamDetails = await TeamsInfo.GetTeamDetailsAsync(stepContext.Context, stepContext.Context.Activity.TeamsGetTeamInfo().Id, cancellationToken); } - catch (Exception ex) + catch (Exception) { await stepContext.Context.SendActivityAsync( "You don't have Graph API permissions to fetch tag's information. Please use this command to mention a tag: \"`@ @`\" to experience tag mention using bot.", diff --git a/src/samples/Teams/bot-tag-mention/Program.cs b/src/samples/Teams/bot-tag-mention/Program.cs index c6b66823..3894442d 100644 --- a/src/samples/Teams/bot-tag-mention/Program.cs +++ b/src/samples/Teams/bot-tag-mention/Program.cs @@ -11,8 +11,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Teams; var builder = WebApplication.CreateBuilder(args); @@ -26,7 +27,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot>(); +builder.AddBot, CloudAdapter, TeamsChannelServiceClientFactory>(); builder.Services.AddSingleton((sp) => { diff --git a/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj b/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj index 291f80a1..27f37a54 100644 --- a/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj +++ b/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj @@ -32,6 +32,7 @@ + diff --git a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs index e2d31ca9..7057d6fe 100644 --- a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs @@ -3,12 +3,12 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.State; using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Teams.Compat; namespace TeamsAuth.Bots { diff --git a/src/samples/Teams/bot-teams-authentication/Program.cs b/src/samples/Teams/bot-teams-authentication/Program.cs index e0662c8f..815e4297 100644 --- a/src/samples/Teams/bot-teams-authentication/Program.cs +++ b/src/samples/Teams/bot-teams-authentication/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Hosting; using TeamsAuth.Bots; using TeamsAuth.Dialogs; +using Microsoft.Agents.Teams; var builder = WebApplication.CreateBuilder(args); @@ -20,7 +21,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot>(); +builder.AddBot, CloudAdapter, TeamsChannelServiceClientFactory>(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj b/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj index 193a4354..db9d385f 100644 --- a/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj +++ b/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj @@ -13,6 +13,7 @@ + diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs index 89c2efa5..3779ea60 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceClientFactoryTests.cs @@ -11,7 +11,6 @@ using Moq; using System.Threading; using System.Net.Http; -using Microsoft.Agents.Teams.Connector; using Microsoft.Agents.Connector; namespace Microsoft.Agents.BotBuilder.Tests @@ -94,7 +93,7 @@ public async Task ConnectionMapNotFoundAnonymousDoesNotThrowAsync() var factory = new RestChannelServiceClientFactory(config, httpFactory.Object, connections); var connector = await factory.CreateConnectorClientAsync(new System.Security.Claims.ClaimsIdentity(), "http://serviceurl", string.Empty, CancellationToken.None, useAnonymous: true); - Assert.IsType(connector); + Assert.IsType(connector); var tokeClient = await factory.CreateUserTokenClientAsync(new System.Security.Claims.ClaimsIdentity(), CancellationToken.None, useAnonymous: true); Assert.IsType(tokeClient); @@ -155,7 +154,7 @@ public async Task ConnectionFoundAsync() var factory = new RestChannelServiceClientFactory(config, httpFactory.Object, connections); var connector = await factory.CreateConnectorClientAsync(new System.Security.Claims.ClaimsIdentity(), "http://serviceurl", "audience", CancellationToken.None, useAnonymous: true); - Assert.IsType(connector); + Assert.IsType(connector); var tokeClient = await factory.CreateUserTokenClientAsync(new System.Security.Claims.ClaimsIdentity(), CancellationToken.None, useAnonymous: true); Assert.IsType(tokeClient); @@ -193,7 +192,7 @@ public async Task ConnectionFoundWithConfigTokenEndpointAsync() var factory = new RestChannelServiceClientFactory(config, httpFactory.Object, connections); var connector = await factory.CreateConnectorClientAsync(new System.Security.Claims.ClaimsIdentity(), "http://serviceurl", "audience", CancellationToken.None, useAnonymous: true); - Assert.IsType(connector); + Assert.IsType(connector); var tokeClient = await factory.CreateUserTokenClientAsync(new System.Security.Claims.ClaimsIdentity(), CancellationToken.None, useAnonymous: true); Assert.IsType(tokeClient); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj index 669a123f..e8bc8bed 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/NormalizeMentionsMiddlewareTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/NormalizeMentionsMiddlewareTests.cs index 4b31e11e..58f6fd7f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/NormalizeMentionsMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/NormalizeMentionsMiddlewareTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Models; using Xunit; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs index d8060aeb..e42e5af7 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs @@ -8,9 +8,10 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using Xunit; -using Microsoft.Agents.BotBuilder.SharePoint; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.SharePoint.Models; +using Microsoft.Agents.SharePoint; +using Microsoft.Agents.SharePoint.Compat; namespace Microsoft.Agents.BotBuilder.Tests.SharePoint { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs index fce6ab7d..e506b483 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Models; using System; diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs index 6a268053..68c8a648 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs index 16856ff5..4acb86fe 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Teams; using Microsoft.Agents.Teams.Models; using Xunit; diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs rename to src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs index f541384f..2b435f2e 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RestTeamsOperationsTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.Connector.Tests.Teams +namespace Microsoft.Agents.Teams.Tests { public class RestTeamsOperationsTests { diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs similarity index 98% rename from src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs rename to src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs index 45fa148a..66700102 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryActionTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.Connector.Tests.Teams +namespace Microsoft.Agents.Teams.Tests { public class RetryActionTests { diff --git a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs rename to src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs index 186ea7a0..f11d43fc 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/Teams/RetryParamTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs @@ -5,7 +5,7 @@ using System; using Xunit; -namespace Microsoft.Agents.Connector.Tests.Teams +namespace Microsoft.Agents.Teams.Tests { public class RetryParamTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj b/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj index 3c230c34..40db90bb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj +++ b/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj @@ -14,7 +14,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,5 +24,6 @@ + diff --git a/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs b/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs new file mode 100644 index 00000000..0d90a6a6 --- /dev/null +++ b/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs @@ -0,0 +1,17 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.Teams.Tests +{ + internal class NotImplementedAdapter : ChannelAdapter + { + public override Task SendActivitiesAsync(ITurnContext turnContext, IActivity[] activities, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs b/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs new file mode 100644 index 00000000..178f5f5b --- /dev/null +++ b/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.Core.Models; +using Xunit; + +namespace Microsoft.Agents.Teams.Tests +{ + public class SimpleAdapter : ChannelAdapter + { + private readonly Action _callOnSend = null; + private readonly Action _callOnUpdate = null; + private readonly Action _callOnDelete = null; + + public SimpleAdapter() + { + } + + public SimpleAdapter(Action callOnSend) + { + _callOnSend = callOnSend; + } + + public SimpleAdapter(Action callOnUpdate) + { + _callOnUpdate = callOnUpdate; + } + + public SimpleAdapter(Action callOnDelete) + { + _callOnDelete = callOnDelete; + } + + public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) + { + Assert.NotNull(reference); // SimpleAdapter.deleteActivity: missing reference + _callOnDelete?.Invoke(reference); + return Task.CompletedTask; + } + + public override Task SendActivitiesAsync(ITurnContext turnContext, IActivity[] activities, CancellationToken cancellationToken) + { + Assert.NotNull(activities); // SimpleAdapter.deleteActivity: missing reference + Assert.True(activities.Count() > 0, "SimpleAdapter.sendActivities: empty activities array."); + + _callOnSend?.Invoke(activities); + List responses = new List(); + + foreach (var activity in activities) + { + responses.Add(new ResourceResponse(activity.Id)); + } + + return Task.FromResult(responses.ToArray()); + } + + public override Task UpdateActivityAsync(ITurnContext turnContext, IActivity activity, CancellationToken cancellationToken) + { + Assert.NotNull(activity); //SimpleAdapter.updateActivity: missing activity + _callOnUpdate?.Invoke(activity); + return Task.FromResult(new ResourceResponse(activity.Id)); // echo back the Id + } + + public async Task ProcessRequest(Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken) + { + using (var ctx = new TurnContext(this, activity)) + { + await this.RunPipelineAsync(ctx, callback, cancellationToken); + } + } + } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs b/src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs index 3b66d583..582f001b 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TeamsActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs @@ -9,8 +9,9 @@ using Microsoft.Agents.Teams.Models; using System; using System.Globalization; +using Microsoft.Agents.BotBuilder; -namespace Microsoft.Agents.BotBuilder.Tests.Teams +namespace Microsoft.Agents.Teams.Tests { public class TeamsActivityHandlerTests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs b/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs similarity index 99% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs rename to src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs index bfacf493..4154e3d9 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Teams/TestActivityHandler.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Teams; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Reflection; @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Tests.Teams +namespace Microsoft.Agents.Teams.Tests { internal class TestActivityHandler : TeamsActivityHandler { From 3f57702e5afe88181d61b62c3de6b066fb44b9ed Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Feb 2025 16:29:42 -0600 Subject: [PATCH 11/60] Application iteration --- .../AdaptiveCards/AdaptiveCards.cs | 56 +++++---- .../Application/Application.cs | 82 ++++++------- .../AuthException.cs | 2 +- .../Authentication/IAuthentication.cs | 109 ++++++++++++++++++ .../Application/ConversationUpdateEvents.cs | 2 +- .../Exceptions/InvokeResponseException.cs | 37 ------ .../Exceptions/TeamsAIException.cs | 30 ----- .../{Route => }/MultipleRouteSelector.cs | 2 +- .../Application/{Route => }/Route.cs | 2 +- .../Application/State/Record.cs | 8 +- .../Application/State/TurnState.cs | 22 ++-- .../Application/State/TurnStateEntry.cs | 8 +- .../Application/StreamingResponse.cs | 9 +- .../Application/TypingTimer.cs | 2 +- .../Application/Verify.cs | 41 ------- .../ChannelServiceAdapterBase.cs | 2 - .../Microsoft.Agents.BotBuilder/OAuthFlow.cs | 2 +- ...nInit.cs => SerializationInitAttribute.cs} | 0 .../Core/Microsoft.Agents.State/BotState.cs | 11 ++ .../Microsoft.Agents.State/BotStateSet.cs | 17 ++- .../Core/Microsoft.Agents.State/IBotState.cs | 1 + .../Core/Microsoft.Agents.State/ITurnState.cs | 30 +++++ .../Core/Microsoft.Agents.State/TempState.cs | 13 +-- .../Application/Meetings/Meetings.cs | 9 +- .../MessageExtensions/MessageExtensions.cs | 96 ++++++++------- .../Application/TaskModules/TaskModules.cs | 38 +++--- .../Application/TeamsApplication.cs | 23 ++-- .../TeamsConversationUpdateEvents.cs | 16 +-- .../messaging.echoBot/EchoBotApplication.cs | 6 +- .../messaging.echoBot/StateExtensions.cs | 19 +++ 30 files changed, 371 insertions(+), 324 deletions(-) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/{Exceptions => Authentication}/AuthException.cs (95%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/{Route => }/MultipleRouteSelector.cs (92%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/{Route => }/Route.cs (97%) delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs rename src/libraries/Core/Microsoft.Agents.Core/Serialization/{SerializationInit.cs => SerializationInitAttribute.cs} (100%) create mode 100644 src/libraries/Core/Microsoft.Agents.State/ITurnState.cs create mode 100644 src/samples/Application/messaging.echoBot/StateExtensions.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs index 3ba0564c..611df725 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.Exceptions; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; @@ -55,8 +53,8 @@ public AdaptiveCards(Application app) /// The application instance for chaining purposes. public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handler) { - Verify.ParamNotNull(verb); - Verify.ParamNotNull(handler); + ArgumentException.ThrowIfNullOrWhiteSpace(verb); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateActionExecuteSelector((string input) => string.Equals(verb, input)); return OnActionExecute(routeSelector, handler); } @@ -69,8 +67,8 @@ public Application OnActionExecute(string verb, ActionExecuteHandlerAsyn /// The application instance for chaining purposes. public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) { - Verify.ParamNotNull(verbPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(verbPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateActionExecuteSelector((string input) => verbPattern.IsMatch(input)); return OnActionExecute(routeSelector, handler); } @@ -83,8 +81,8 @@ public Application OnActionExecute(Regex verbPattern, ActionExecuteHandl /// The application instance for chaining purposes. public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { AdaptiveCardInvokeValue? invokeValue; @@ -94,7 +92,7 @@ public Application OnActionExecute(RouteSelectorAsync routeSelector, Act || invokeValue.Action == null || !string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE)) { - throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: {turnContext.Activity.Type}"); } AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = await handler(turnContext, turnState, invokeValue.Action.Data, cancellationToken); @@ -113,8 +111,8 @@ public Application OnActionExecute(RouteSelectorAsync routeSelector, Act /// The application instance for chaining purposes. public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string verb in routeSelectors.Strings) @@ -164,8 +162,8 @@ public Application OnActionExecute(MultipleRouteSelector routeSelectors, /// The application instance for chaining purposes. public Application OnActionSubmit(string verb, ActionSubmitHandler handler) { - Verify.ParamNotNull(verb); - Verify.ParamNotNull(handler); + ArgumentException.ThrowIfNullOrWhiteSpace(verb); + ArgumentNullException.ThrowIfNull(handler); string filter = _app.Options.AdaptiveCards?.ActionSubmitFilter ?? DEFAULT_ACTION_SUBMIT_FILTER; RouteSelectorAsync routeSelector = CreateActionSubmitSelector((string input) => string.Equals(verb, input), filter); return OnActionSubmit(routeSelector, handler); @@ -196,8 +194,8 @@ public Application OnActionSubmit(string verb, ActionSubmitHandlerThe application instance for chaining purposes. public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) { - Verify.ParamNotNull(verbPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(verbPattern); + ArgumentNullException.ThrowIfNull(handler); string filter = _app.Options.AdaptiveCards?.ActionSubmitFilter ?? DEFAULT_ACTION_SUBMIT_FILTER; RouteSelectorAsync routeSelector = CreateActionSubmitSelector((string input) => verbPattern.IsMatch(input), filter); return OnActionSubmit(routeSelector, handler); @@ -228,15 +226,15 @@ public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler /// The application instance for chaining purposes. public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(turnContext.Activity.Text) || turnContext.Activity.Value == null) { - throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionSubmit() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected AdaptiveCards.OnActionSubmit() triggered for activity type: {turnContext.Activity.Type}"); } await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); @@ -270,8 +268,8 @@ public Application OnActionSubmit(RouteSelectorAsync routeSelector, Acti /// The application instance for chaining purposes. public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string verb in routeSelectors.Strings) @@ -304,8 +302,8 @@ public Application OnActionSubmit(MultipleRouteSelector routeSelectors, /// The application instance for chaining purposes. public Application OnSearch(string dataset, SearchHandlerAsync handler) { - Verify.ParamNotNull(dataset); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(dataset); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateSearchSelector((string input) => string.Equals(dataset, input)); return OnSearch(routeSelector, handler); } @@ -318,8 +316,8 @@ public Application OnSearch(string dataset, SearchHandlerAsync h /// The application instance for chaining purposes. public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) { - Verify.ParamNotNull(datasetPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(datasetPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateSearchSelector((string input) => datasetPattern.IsMatch(input)); return OnSearch(routeSelector, handler); } @@ -332,8 +330,8 @@ public Application OnSearch(Regex datasetPattern, SearchHandlerAsyncThe application instance for chaining purposes. public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { AdaptiveCardSearchInvokeValue? searchInvokeValue; @@ -341,7 +339,7 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand || !string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME) || (searchInvokeValue = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { - throw new TeamsAIException($"Unexpected AdaptiveCards.OnSearch() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected AdaptiveCards.OnSearch() triggered for activity type: {turnContext.Activity.Type}"); } AdaptiveCardsSearchParams adaptiveCardsSearchParams = new(searchInvokeValue.QueryText, searchInvokeValue.Dataset ?? string.Empty); @@ -376,8 +374,8 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand /// The application instance for chaining purposes. public Application OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string verb in routeSelectors.Strings) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs index 0f4046e9..e7856795 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; @@ -28,9 +27,9 @@ public class Application : IBot private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; + // TODO: These really aren't queues, so why this type? private readonly ConcurrentQueue> _invokeRoutes; private readonly ConcurrentQueue> _routes; - private readonly ConcurrentQueue> _beforeTurn; private readonly ConcurrentQueue> _afterTurn; @@ -44,7 +43,8 @@ public class Application : IBot /// public Application(ApplicationOptions options) { - Verify.ParamNotNull(options); + ArgumentNullException.ThrowIfNull(options); + Options = options; @@ -126,8 +126,8 @@ public AuthenticationManager Authentication /// The application instance for chaining purposes. public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) { - Verify.ParamNotNull(selector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(selector); + ArgumentNullException.ThrowIfNull(handler); Route route = new(selector, handler, isInvokeRoute); if (isInvokeRoute) { @@ -148,8 +148,8 @@ public Application AddRoute(RouteSelectorAsync selector, RouteHandlerThe application instance for chaining purposes. public Application OnActivity(string type, RouteHandler handler) { - Verify.ParamNotNull(type); - Verify.ParamNotNull(handler); + ArgumentException.ThrowIfNullOrWhiteSpace(type); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(string.Equals(type, context.Activity?.Type, StringComparison.OrdinalIgnoreCase)); OnActivity(routeSelector, handler); return this; @@ -163,8 +163,8 @@ public Application OnActivity(string type, RouteHandler handler) /// The application instance for chaining purposes. public Application OnActivity(Regex typePattern, RouteHandler handler) { - Verify.ParamNotNull(typePattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(typePattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(context.Activity?.Type != null && typePattern.IsMatch(context.Activity?.Type)); OnActivity(routeSelector, handler); return this; @@ -178,8 +178,8 @@ public Application OnActivity(Regex typePattern, RouteHandler ha /// The application instance for chaining purposes. public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); AddRoute(routeSelector, handler, isInvokeRoute: false); return this; } @@ -192,8 +192,8 @@ public Application OnActivity(RouteSelectorAsync routeSelector, RouteHan /// The application instance for chaining purposes. public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string type in routeSelectors.Strings) @@ -226,8 +226,8 @@ public Application OnActivity(MultipleRouteSelector routeSelectors, Rout /// The application instance for chaining purposes. public virtual Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) { - Verify.ParamNotNull(conversationUpdateEvent); - Verify.ParamNotNull(handler); + ArgumentException.ThrowIfNullOrWhiteSpace(conversationUpdateEvent); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector; switch (conversationUpdateEvent) @@ -273,8 +273,8 @@ public virtual Application OnConversationUpdate(string conversationUpdat /// The application instance for chaining purposes. public Application OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler) { - Verify.ParamNotNull(conversationUpdateEvents); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(conversationUpdateEvents); + ArgumentNullException.ThrowIfNull(handler); foreach (string conversationUpdateEvent in conversationUpdateEvents) { OnConversationUpdate(conversationUpdateEvent, handler); @@ -297,8 +297,8 @@ public Application OnConversationUpdate(string[] conversationUpdateEvent /// The application instance for chaining purposes. public Application OnMessage(string text, RouteHandler handler) { - Verify.ParamNotNull(text); - Verify.ParamNotNull(handler); + ArgumentException.ThrowIfNullOrWhiteSpace(text); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( @@ -325,8 +325,8 @@ public Application OnMessage(string text, RouteHandler handler) /// The application instance for chaining purposes. public Application OnMessage(Regex textPattern, RouteHandler handler) { - Verify.ParamNotNull(textPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(textPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( @@ -349,8 +349,8 @@ public Application OnMessage(Regex textPattern, RouteHandler han /// The application instance for chaining purposes. public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); AddRoute(routeSelector, handler, isInvokeRoute: false); return this; } @@ -366,8 +366,8 @@ public Application OnMessage(RouteSelectorAsync routeSelector, RouteHand /// The application instance for chaining purposes. public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string text in routeSelectors.Strings) @@ -399,7 +399,7 @@ public Application OnMessage(MultipleRouteSelector routeSelectors, Route /// The application instance for chaining purposes. public Application OnMessageReactionsAdded(RouteHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.MessageReaction, StringComparison.OrdinalIgnoreCase) @@ -417,7 +417,7 @@ public Application OnMessageReactionsAdded(RouteHandler handler) /// The application instance for chaining purposes. public Application OnMessageReactionsRemoved(RouteHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.MessageReaction, StringComparison.OrdinalIgnoreCase) @@ -435,7 +435,7 @@ public Application OnMessageReactionsRemoved(RouteHandler handle /// The application instance for chaining purposes. public Application OnHandoff(HandoffHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -470,7 +470,7 @@ public Application OnHandoff(HandoffHandler handler) /// The application instance for chaining purposes. public Application OnBeforeTurn(TurnEventHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); _beforeTurn.Enqueue(handler); return this; } @@ -485,7 +485,7 @@ public Application OnBeforeTurn(TurnEventHandlerAsync handler) /// The application instance for chaining purposes. public Application OnAfterTurn(TurnEventHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); _afterTurn.Enqueue(handler); return this; } @@ -500,20 +500,8 @@ public Application OnAfterTurn(TurnEventHandlerAsync handler) /// A task that represents the work queued to execute. public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - if (turnContext == null) - { - throw new ArgumentNullException(nameof(turnContext)); - } - - if (turnContext.Activity == null) - { - throw new ArgumentException($"{nameof(turnContext)} must have non-null Activity."); - } - - if (turnContext.Activity.Type == null) - { - throw new ArgumentException($"{nameof(turnContext)}.Activity must have non-null Type."); - } + ArgumentNullException.ThrowIfNull(turnContext); + ArgumentNullException.ThrowIfNull(turnContext.Activity); await _OnTurnAsync(turnContext, cancellationToken); } @@ -575,6 +563,8 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // Remove @mentions if (Options.RemoveRecipientMention && ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { + // TODO: What about normalizing mentions? + turnContext.Activity.Text = turnContext.Activity.RemoveRecipientMention(); } @@ -637,15 +627,18 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } } + /* // Populate {{$temp.input}} if ((turnState.Temp.Input == null || turnState.Temp.Input.Length == 0) && turnContext.Activity.Text != null) { // Use the received activity text turnState.Temp.Input = turnContext.Activity.Text; } + */ bool eventHandlerCalled = false; + // TODO: why is this needed? Would not the selector be limiting to "Invoke" anyway, so iterating _routes would be the same thing. // Run any RouteSelectors in this._invokeRoutes first if the incoming Teams activity.type is "Invoke". // Invoke Activities from Teams need to be responded to in less than 5 seconds. if (ActivityTypes.Invoke.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) @@ -683,6 +676,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc return; } } + await turnState!.SaveStateAsync(turnContext, storage); } finally diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs index be99abf3..e422db52 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/AuthException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.BotBuilder.Application.Exceptions +namespace Microsoft.Agents.BotBuilder.Application.Authentication { /// /// Cause of user authentication exception. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs new file mode 100644 index 00000000..1863fb67 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.Core.Interfaces; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.Application.Authentication +{ + /// + /// The sign-in status + /// + public enum SignInStatus + { + /// + /// Sign-in not complete and requires user interaction + /// + Pending, + + /// + /// Sign-in complete + /// + Complete, + + /// + /// Error occurred during sign-in + /// + Error + } + + /// + /// The sign-in response + /// + public class SignInResponse + { + /// + /// The sign-in status + /// + public SignInStatus Status { get; set; } + + /// + /// The exception object. Only available when sign-in status is Error. + /// + public Exception? Error { get; set; } + + /// + /// The cause of error. Only available when sign-in status is Error. + /// + public AuthExceptionReason? Cause { get; set; } + + /// + /// Initialize an instance of current class + /// + /// The sign in status + public SignInResponse(SignInStatus status) + { + this.Status = status; + } + } + + /// + /// Handles user sign-in and sign-out. + /// + public interface IAuthentication + where TState : TurnState, new() + { + /// + /// Signs in a user. + /// This method will be called automatically by the Application class. + /// + /// Current turn context. + /// Application state. + /// The cancellation token + /// The authentication token if user is signed in. Otherwise returns null. In that case the bot will attempt to sign the user in. + Task SignInUserAsync(ITurnContext context, TState state, CancellationToken cancellationToken = default); + + /// + /// Signs out a user. + /// + /// Current turn context. + /// Application state. + /// The cancellation token + Task SignOutUserAsync(ITurnContext context, TState state, CancellationToken cancellationToken = default); + + /// + /// The handler function is called when the user has successfully signed in + /// + /// The handler function to call when the user has successfully signed in + /// The class itself for chaining purpose + IAuthentication OnUserSignInSuccess(Func handler); + + /// + /// The handler function is called when the user sign in flow fails + /// + /// The handler function to call when the user failed to signed in + /// The class itself for chaining purpose + IAuthentication OnUserSignInFailure(Func handler); + + /// + /// Check if the user is signed, if they are then return the token. + /// + /// The turn context. + /// The cancellation token + /// The token if the user is signed. Otherwise null. + Task IsUserSignedInAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs index c32c2b1d..8c4a5ec7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs @@ -6,7 +6,7 @@ namespace Microsoft.Agents.BotBuilder.Application /// /// Conversation update events. /// - public static class ConversationUpdateEvents + public class ConversationUpdateEvents { /// /// MembersAdded event diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs deleted file mode 100644 index 1b6a95a6..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/InvokeResponseException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Net; - -namespace Microsoft.Agents.BotBuilder.Application.Exceptions -{ - /// - /// A custom exception for invoke response errors. - /// - internal class InvokeResponseException : Exception - { - - /// - /// A getter for the status code - /// - public HttpStatusCode StatusCode { get; } - - /// - /// A getter for the body - /// - public object? Body { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The Http status code of the error. - /// The body of the exception. Default is null. - /// The inner exception. Default is null. - public InvokeResponseException(HttpStatusCode statusCode, object? body = null, Exception? innerException = null) : base("InvokeResponseException", innerException) - { - StatusCode = statusCode; - Body = body; - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs deleted file mode 100644 index 87680324..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Exceptions/TeamsAIException.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.Agents.BotBuilder.Application.Exceptions -{ - /// - /// Base exception for the TeamsAI library. - /// - public class TeamsAIException : Exception - { - /// - /// Create an instance of the class. - /// - /// Exception message. - public TeamsAIException(string message) : base(message) - { - } - - /// - /// Create an instance of the class. - /// - /// Exception message. - /// Inner exception. - public TeamsAIException(string message, Exception innerException) : base(message, innerException) - { - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs similarity index 92% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs index 48dc7a23..2b0c7d7f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/MultipleRouteSelector.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; -namespace Microsoft.Agents.BotBuilder.Application.Route +namespace Microsoft.Agents.BotBuilder.Application { /// /// Combination of String, Regex, and RouteSelectorAsync selectors. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs index b982872a..df49b445 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route/Route.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.Route +namespace Microsoft.Agents.BotBuilder.Application { /// /// Function for selecting whether a route handler should be triggered. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs index d53c5c42..0be910f5 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs @@ -22,7 +22,7 @@ public class Record : Dictionary /// public bool TryGetValue(string key, out T value) { - Verify.ParamNotNull(key); + ArgumentException.ThrowIfNullOrWhiteSpace(key); if (base.TryGetValue(key, out object entry)) { @@ -52,7 +52,7 @@ public bool TryGetValue(string key, out T value) /// The value associated with the key public T? Get(string key) { - Verify.ParamNotNull(key); + ArgumentException.ThrowIfNullOrWhiteSpace(key); if (TryGetValue(key, out T value)) { @@ -72,8 +72,8 @@ public bool TryGetValue(string key, out T value) /// value associated with key public void Set(string key, T value) { - Verify.ParamNotNull(key); - Verify.ParamNotNull(value); + ArgumentException.ThrowIfNullOrWhiteSpace(key); + ArgumentNullException.ThrowIfNull(value); #pragma warning disable CS8601 // Possible null reference assignment. this[key] = value; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs index b90fa0cc..22f06abc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.Exceptions; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Storage; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; @@ -92,7 +92,7 @@ public Record Conversation } set { - Verify.ParamNotNull(value); + ArgumentNullException.ThrowIfNull(value); TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); if (scope == null) @@ -121,7 +121,7 @@ public Record User } set { - Verify.ParamNotNull(value); + ArgumentNullException.ThrowIfNull(value); TurnStateEntry? scope = GetScope(USER_SCOPE); if (scope == null) @@ -150,7 +150,7 @@ public TempState Temp } set { - Verify.ParamNotNull(value); + ArgumentNullException.ThrowIfNull(value); TurnStateEntry? scope = GetScope(TEMP_SCOPE); if (scope == null) @@ -329,7 +329,7 @@ public async Task LoadStateAsync(IStorage? storage, ITurnContext turnConte catch (Exception ex) { this._loadingTask = null; - throw new TeamsAIException($"Something went wrong when loading state: {ex.Message}", ex); + throw new InvalidOperationException($"Something went wrong when loading state: {ex.Message}", ex); } }); } @@ -344,7 +344,7 @@ public async Task LoadStateAsync(IStorage? storage, ITurnContext turnConte /// Optional. Storage provider to save state scopes to. public async Task SaveStateAsync(ITurnContext turnContext, IStorage? storage) { - Verify.ParamNotNull(turnContext); + ArgumentNullException.ThrowIfNull(turnContext); // Check for existing load operation if (!this._isLoaded && this._loadingTask!.Result) @@ -433,11 +433,11 @@ protected virtual Dictionary OnComputeStorageKeys(ITurnContext c string conversationId = activity.Conversation.Id; string userId = activity.From.Id; - Verify.ParamNotNull(activity, "TurnContext.Activity"); - Verify.ParamNotNull(channelId, "TurnContext.Activity.ChannelId"); - Verify.ParamNotNull(botId, "TurnContext.Activity.Recipient.Id"); - Verify.ParamNotNull(conversationId, "TurnContext.Activity.Conversation.Id"); - Verify.ParamNotNull(userId, "TurnContext.Activity.From.Id"); + ArgumentNullException.ThrowIfNull(activity, "TurnContext.Activity"); + ArgumentException.ThrowIfNullOrWhiteSpace(channelId, "TurnContext.Activity.ChannelId"); + ArgumentException.ThrowIfNullOrWhiteSpace(botId, "TurnContext.Activity.Recipient.Id"); + ArgumentException.ThrowIfNullOrWhiteSpace(conversationId, "TurnContext.Activity.Conversation.Id"); + ArgumentException.ThrowIfNullOrWhiteSpace(userId, "TurnContext.Activity.From.Id"); string conversationKey = $"{channelId}/${botId}/conversations/${conversationId}"; string userKey = $"{channelId}/${botId}/users/${userId}"; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs index c896837b..ecaf91d6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Newtonsoft.Json.Linq; +using System; using System.Text.Json; namespace Microsoft.Agents.BotBuilder.Application.State @@ -21,7 +23,7 @@ public class TurnStateEntry /// Storage key to use when persisting the state scope. public TurnStateEntry(Record value, string? storageKey = null) { - Verify.ParamNotNull(value); + ArgumentNullException.ThrowIfNull(value); _value = value; StorageKey = storageKey; _hash = ComputeHash(value); @@ -68,7 +70,7 @@ public void Delete() /// New value to replace the state scope with. public void Replace(Record value) { - Verify.ParamNotNull(value); + ArgumentNullException.ThrowIfNull(value); _value = value; } @@ -80,7 +82,7 @@ public void Replace(Record value) /// Returns a Json object representation internal static string ComputeHash(object obj) { - Verify.ParamNotNull(obj); + ArgumentNullException.ThrowIfNull(obj); return JsonSerializer.Serialize(obj, _serializerOptions); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs index adc94e95..b9508195 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.Exceptions; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; @@ -92,7 +91,7 @@ public void QueueInformativeUpdate(string text) { if (this._ended) { - throw new TeamsAIException("The stream has already ended."); + throw new InvalidOperationException("The stream has already ended."); } var activity = new Activity @@ -121,7 +120,7 @@ public void QueueTextChunk(string text) //, IList? citations = null) { if (this._ended) { - throw new TeamsAIException("The stream has already ended."); + throw new InvalidOperationException("The stream has already ended."); } Message += text; @@ -173,7 +172,7 @@ public Task EndStream() { if (this._ended) { - throw new TeamsAIException("The stream has already ended."); + throw new InvalidOperationException("The stream has already ended."); } this._ended = true; @@ -200,7 +199,7 @@ private void QueueActivity(Func factory) { Exception ex = this._queueSync.Exception; this._queueSync = null; - throw new TeamsAIException($"Error occurred when sending activity while streaming", ex); + throw new InvalidOperationException($"Error occurred when sending activity while streaming", ex); } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs index 200d23d7..8eb3b110 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs @@ -46,7 +46,7 @@ public TypingTimer(int interval = 1000) /// True if the timer was started, otherwise False. public bool Start(ITurnContext turnContext) { - Verify.ParamNotNull(turnContext); + ArgumentNullException.ThrowIfNull(turnContext); if (turnContext.Activity.Type != ActivityTypes.Message || IsRunning()) { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs deleted file mode 100644 index 48835248..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Verify.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Runtime.CompilerServices; - -namespace Microsoft.Agents.BotBuilder.Application -{ - /// - /// Utility class for verifying arguments and local variables. - /// - public class Verify - { - /// - /// Verifies that the argument is not null. - /// - /// An arbitrary object. - /// Optional. The name of the parameter. If not populated, it defaults to the name of the variable passed to the parameter. - /// - public static void ParamNotNull(object? argument, [CallerArgumentExpression("argument")] string? parameterName = default) - { - if (argument == null) - { - throw new ArgumentNullException(parameterName); - } - } - - /// - /// Verifies that the local variable is not null. - /// - /// An arbitrary object. - /// - public static void NotNull(object? variable) - { - if (variable == null) - { - throw new ArgumentNullException(nameof(variable)); - } - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index ead8df3e..36a3bbf8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -331,8 +331,6 @@ private TurnContext CreateTurnContext(IActivity activity, ClaimsIdentity claimsI turnContext.TurnState.Add(BotIdentityKey, claimsIdentity); turnContext.TurnState.Add(connectorClient); turnContext.TurnState.Add(userTokenClient); - turnContext.TurnState.Add(callback); - turnContext.TurnState.Add(ChannelServiceFactory); turnContext.TurnState.Set(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set return turnContext; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs index 9fdfa0b8..626d4728 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs @@ -49,7 +49,7 @@ public class OAuthFlow(string title, string text, string connectionName, int? ti public int? Timeout { get; init; } = timeout; public bool? ShowSignInLink { get; init; } = showSignInLink; - public virtual async Task BeginFlowAsync(ITurnContext turnContext, IActivity prompt, CancellationToken cancellationToken = default) + public virtual async Task BeginFlowAsync(ITurnContext turnContext, IActivity? prompt, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(turnContext); diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInitAttribute.cs similarity index 100% rename from src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInit.cs rename to src/libraries/Core/Microsoft.Agents.Core/Serialization/SerializationInitAttribute.cs diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs index 3504332a..ae3b6286 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotState.cs @@ -68,6 +68,17 @@ public IStatePropertyAccessor CreateProperty(string name) return new BotStatePropertyAccessor(this, name); } + public bool HasValue(string name) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + var cachedState = GetCachedState(); + return ObjectPath.HasValue(cachedState.State, name); + } + /// /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs index dc162525..e1e678c3 100644 --- a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs +++ b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs @@ -8,13 +8,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; namespace Microsoft.Agents.State { /// /// Manages a collection of botState and provides ability to load and save in parallel. /// - public class BotStateSet + public class BotStateSet : ITurnState { private readonly Dictionary _scopes = []; @@ -52,21 +53,27 @@ public BotStateSet(IStorage storage, params IBotState[] botStates) public PrivateConversationState Private => GetScope(); public TempState Temp => GetScope(); + public bool HasValue(string path) + { + var (scope, property) = GetScopeAndPath(path); + return GetScope(scope).HasValue(property); + } + public T GetValue(string name, Func defaultValueFactory = null) { var (scope, property) = GetScopeAndPath(name); return GetScope(scope).GetValue(property, defaultValueFactory); } - public void SetValue(string name, object value) + public void SetValue(string path, object value) { - var (scope, property) = GetScopeAndPath(name); + var (scope, property) = GetScopeAndPath(path); GetScope(scope).SetValue(property, value); } - public void DeleteValue(string name) + public void DeleteValue(string path) { - var (scope, property) = GetScopeAndPath(name); + var (scope, property) = GetScopeAndPath(path); GetScope(scope).DeleteValue(property); } diff --git a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs index 8f2a89bb..95c38537 100644 --- a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs @@ -20,5 +20,6 @@ public interface IBotState Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); void SetValue(string name, T value); + bool HasValue(string name); } } \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs b/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs new file mode 100644 index 00000000..c7a924d4 --- /dev/null +++ b/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Agents.Core.Interfaces; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.State +{ + public interface ITurnState + { + ConversationState Conversation { get; } + PrivateConversationState Private { get; } + TempState Temp { get; } + UserState User { get; } + + IBotState GetScope(string scope); + T GetScope(); + + T GetValue(string path, Func defaultValueFactory = null); + void SetValue(string path, object value); + void DeleteValue(string path); + bool HasValue(string path); + + void ClearState(string scope); + Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/TempState.cs b/src/libraries/Core/Microsoft.Agents.State/TempState.cs index 7cce1825..c89000c6 100644 --- a/src/libraries/Core/Microsoft.Agents.State/TempState.cs +++ b/src/libraries/Core/Microsoft.Agents.State/TempState.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; -using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -22,10 +20,6 @@ public class TempState : IBotState public string Name => ScopeName; - public string AuthScope { get { return GetValue(AuthScopeKey); } set { SetValue(AuthScopeKey, value); } } - public ClaimsIdentity BotIdentity { get { return GetValue(BotIdentityKey); } set { SetValue(BotIdentityKey, value); } } - public IActivity InvokeResponse { get { return GetValue(InvokeResponseKey); } set { SetValue(InvokeResponseKey, value); } } - public void ClearState() { _state.Clear(); @@ -37,9 +31,14 @@ public Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancell return Task.CompletedTask; } + public bool HasValue(string name) + { + return ObjectPath.HasValue(_state, name); + } + public void DeleteValue(string name) { - _state.Remove(name); + ObjectPath.RemovePathValue(_state, name); } public T GetValue(string name, Func defaultValueFactory = null) diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs index 91ca719a..aca159db 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs @@ -1,5 +1,4 @@ using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; @@ -34,7 +33,7 @@ public Meetings(Application app) /// The application instance for chaining purposes. public Application OnStart(MeetingStartHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) @@ -57,7 +56,7 @@ public Application OnStart(MeetingStartHandler handler) /// The application instance for chaining purposes. public Application OnEnd(MeetingEndHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) @@ -80,7 +79,7 @@ public Application OnEnd(MeetingEndHandler handler) /// The application instance for chaining purposes. public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) @@ -103,7 +102,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandlerThe application instance for chaining purposes. public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs index c34b0a7f..ca199f54 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs @@ -1,8 +1,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Application; using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -using Microsoft.Agents.BotBuilder.Application.Exceptions; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; @@ -71,8 +69,8 @@ public MessageExtensions(Application app) /// The application instance for chaining purposes. public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) { - Verify.ParamNotNull(commandId); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandId); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME); return OnSubmitAction(routeSelector, handler); } @@ -85,8 +83,8 @@ public Application OnSubmitAction(string commandId, SubmitActionHandlerA /// The application instance for chaining purposes. public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) { - Verify.ParamNotNull(commandIdPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandIdPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME); return OnSubmitAction(routeSelector, handler); } @@ -106,7 +104,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { - throw new TeamsAIException($"Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: {turnContext.Activity.Type}"); } MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.Data, cancellationToken); @@ -130,8 +128,8 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm /// The application instance for chaining purposes. public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string commandId in routeSelectors.Strings) @@ -165,8 +163,8 @@ public Application OnSubmitAction(MultipleRouteSelector routeSelectors, /// The application instance for chaining purposes. public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) { - Verify.ParamNotNull(commandId); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandId); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME, "edit"); return OnBotMessagePreviewEdit(routeSelector, handler); } @@ -180,8 +178,8 @@ public Application OnBotMessagePreviewEdit(string commandId, BotMessageP /// The application instance for chaining purposes. public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) { - Verify.ParamNotNull(commandIdPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandIdPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME, "edit"); return OnBotMessagePreviewEdit(routeSelector, handler); } @@ -203,7 +201,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "edit")) { - throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: {turnContext.Activity.Type}"); } MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.BotActivityPreview[0], cancellationToken); @@ -228,8 +226,8 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec /// The application instance for chaining purposes. public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string commandId in routeSelectors.Strings) @@ -263,8 +261,8 @@ public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSe /// The application instance for chaining purposes. public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) { - Verify.ParamNotNull(commandId); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandId); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), SUBMIT_ACTION_INVOKE_NAME, "send"); return OnBotMessagePreviewSend(routeSelector, handler); } @@ -278,8 +276,8 @@ public Application OnBotMessagePreviewSend(string commandId, BotMessageP /// The application instance for chaining purposes. public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) { - Verify.ParamNotNull(commandIdPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandIdPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), SUBMIT_ACTION_INVOKE_NAME, "send"); return OnBotMessagePreviewSend(routeSelector, handler); } @@ -293,8 +291,8 @@ public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMe /// The application instance for chaining purposes. public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { MessagingExtensionAction? messagingExtensionAction; @@ -303,7 +301,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec || (messagingExtensionAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null || !string.Equals(messagingExtensionAction.BotMessagePreviewAction, "send")) { - throw new TeamsAIException($"Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: {turnContext.Activity.Type}"); } Activity activityPreview = messagingExtensionAction.BotActivityPreview.Count > 0 ? messagingExtensionAction.BotActivityPreview[0] : new Activity(); @@ -330,8 +328,8 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec /// The application instance for chaining purposes. public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string commandId in routeSelectors.Strings) @@ -364,8 +362,8 @@ public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSe /// The application instance for chaining purposes. public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) { - Verify.ParamNotNull(commandId); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandId); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME); return OnFetchTask(routeSelector, handler); } @@ -378,8 +376,8 @@ public Application OnFetchTask(string commandId, FetchTaskHandlerAsyncThe application instance for chaining purposes. public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) { - Verify.ParamNotNull(commandIdPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandIdPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME); return OnFetchTask(routeSelector, handler); } @@ -392,14 +390,14 @@ public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerA /// The application instance for chaining purposes. public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME)) { - throw new TeamsAIException($"Unexpected MessageExtensions.OnFetchTask() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected MessageExtensions.OnFetchTask() triggered for activity type: {turnContext.Activity.Type}"); } TaskModuleResponse result = await handler(turnContext, turnState, cancellationToken); @@ -423,8 +421,8 @@ public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTa /// The application instance for chaining purposes. public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string commandId in routeSelectors.Strings) @@ -457,8 +455,8 @@ public Application OnFetchTask(MultipleRouteSelector routeSelectors, Fet /// The application instance for chaining purposes. public Application OnQuery(string commandId, QueryHandlerAsync handler) { - Verify.ParamNotNull(commandId); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandId); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(commandId, input), MessageExtensionsInvokeNames.QUERY_INVOKE_NAME); return OnQuery(routeSelector, handler); } @@ -471,8 +469,8 @@ public Application OnQuery(string commandId, QueryHandlerAsync h /// The application instance for chaining purposes. public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) { - Verify.ParamNotNull(commandIdPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(commandIdPattern); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => commandIdPattern.IsMatch(input), MessageExtensionsInvokeNames.QUERY_INVOKE_NAME); return OnQuery(routeSelector, handler); } @@ -485,8 +483,8 @@ public Application OnQuery(Regex commandIdPattern, QueryHandlerAsyncThe application instance for chaining purposes. public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { MessagingExtensionQuery? messagingExtensionQuery; @@ -494,7 +492,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.QUERY_INVOKE_NAME) || (messagingExtensionQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { - throw new TeamsAIException($"Unexpected MessageExtensions.OnQuery() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected MessageExtensions.OnQuery() triggered for activity type: {turnContext.Activity.Type}"); } IDictionary parameters = new Dictionary(); @@ -528,8 +526,8 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle /// The application instance for chaining purposes. public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { foreach (string commandId in routeSelectors.Strings) @@ -568,7 +566,7 @@ public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHa /// The application instance for chaining purposes. public Application OnSelectItem(SelectItemHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -600,7 +598,7 @@ public Application OnSelectItem(SelectItemHandlerAsync handler) /// The application instance for chaining purposes. public Application OnQueryLink(QueryLinkHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -638,7 +636,7 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) /// The application instance for chaining purposes. public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -674,7 +672,7 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync ha /// The application instance for chaining purposes. public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -709,7 +707,7 @@ public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync /// The application instance for chaining purposes. public Application OnConfigureSettings(ConfigureSettingsHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -742,7 +740,7 @@ public Application OnConfigureSettings(ConfigureSettingsHandler /// The application instance for chaining purposes. public Application OnCardButtonClicked(CardButtonClickedHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs index 10200e1f..e4befee6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs @@ -1,7 +1,5 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.Exceptions; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; @@ -50,8 +48,8 @@ public Application OnFetch(string verb, FetchHandlerAsync handle //TODO /* - Verify.ParamNotNull(verb); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(verb); + ArgumentNullException.ThrowIfNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); @@ -70,8 +68,8 @@ public Application OnFetch(Regex verbPattern, FetchHandlerAsync //TODO /* - Verify.ParamNotNull(verbPattern); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(verbPattern); + ArgumentNullException.ThrowIfNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); @@ -86,8 +84,8 @@ public Application OnFetch(Regex verbPattern, FetchHandlerAsync /// The application instance for chaining purposes. public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { TaskModuleAction? taskModuleAction; @@ -95,7 +93,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle || !string.Equals(turnContext.Activity.Name, FETCH_INVOKE_NAME) || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { - throw new TeamsAIException($"Unexpected TaskModules.OnFetch() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected TaskModules.OnFetch() triggered for activity type: {turnContext.Activity.Type}"); } TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); @@ -120,8 +118,8 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle /// The application instance for chaining purposes. public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { @@ -160,8 +158,8 @@ public Application OnSubmit(string verb, SubmitHandlerAsync hand //TODO /* - Verify.ParamNotNull(verb); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(verb); + ArgumentNullException.ThrowIfNull(handler); string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); @@ -181,8 +179,8 @@ public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync verbPattern.IsMatch(input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); @@ -197,8 +195,8 @@ public Application OnSubmit(Regex verbPattern, SubmitHandlerAsyncThe application instance for chaining purposes. public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelector); + ArgumentNullException.ThrowIfNull(handler); RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => { TaskModuleAction? taskModuleAction; @@ -206,7 +204,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand || !string.Equals(turnContext.Activity.Name, SUBMIT_INVOKE_NAME) || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity)) == null) { - throw new TeamsAIException($"Unexpected TaskModules.OnSubmit() triggered for activity type: {turnContext.Activity.Type}"); + throw new InvalidOperationException($"Unexpected TaskModules.OnSubmit() triggered for activity type: {turnContext.Activity.Type}"); } TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); @@ -231,8 +229,8 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand /// The application instance for chaining purposes. public Application OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(routeSelectors); + ArgumentNullException.ThrowIfNull(handler); if (routeSelectors.Strings != null) { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs index 71114e98..0d34b58c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.Route; using Microsoft.Agents.Teams.Application.Meetings; using Microsoft.Agents.Teams.Application.MessageExtensions; using Microsoft.Agents.Teams.Application.TaskModules; @@ -65,8 +64,8 @@ public TeamsApplication(ApplicationOptions options) : base(options) /// The application instance for chaining purposes. public override Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) { - Verify.ParamNotNull(conversationUpdateEvent); - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(conversationUpdateEvent); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector; switch (conversationUpdateEvent) { @@ -143,7 +142,7 @@ public override Application OnConversationUpdate(string conversationUpda /// The application instance for chaining purposes. public Application OnMessageEdit(RouteHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { TeamsChannelData teamsChannelData; @@ -164,7 +163,7 @@ public Application OnMessageEdit(RouteHandler handler) /// The application instance for chaining purposes. public Application OnMessageUndelete(RouteHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { TeamsChannelData teamsChannelData; @@ -185,7 +184,7 @@ public Application OnMessageUndelete(RouteHandler handler) /// The application instance for chaining purposes. public Application OnMessageDelete(RouteHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => { TeamsChannelData teamsChannelData; @@ -206,7 +205,7 @@ public Application OnMessageDelete(RouteHandler handler) /// The application instance for chaining purposes. public Application OnTeamsReadReceipt(ReadReceiptHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) @@ -229,7 +228,7 @@ public Application OnTeamsReadReceipt(ReadReceiptHandler handler /// The application instance for chaining purposes. public Application OnConfigFetch(ConfigHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, CONFIG_FETCH_INVOKE_NAME) @@ -256,7 +255,7 @@ public Application OnConfigFetch(ConfigHandlerAsync handler) /// The application instance for chaining purposes. public Application OnConfigSubmit(ConfigHandlerAsync handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, CONFIG_SUBMIT_INVOKE_NAME) @@ -294,7 +293,7 @@ public Application OnFileConsentDecline(FileConsentHandler handl private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => { FileConsentCardResponse? fileConsentCardResponse; @@ -329,7 +328,7 @@ private Application OnFileConsent(FileConsentHandler handler, st /// The application instance for chaining purposes. public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -359,7 +358,7 @@ public Application OnO365ConnectorCardAction(O365ConnectorCardActionHand /// public Application OnFeedbackLoop(FeedbackLoopHandler handler) { - Verify.ParamNotNull(handler); + ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs index 1fbef7e3..6a22b36d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs @@ -1,9 +1,11 @@ -namespace Microsoft.Agents.Teams.Application +using Microsoft.Agents.BotBuilder.Application; + +namespace Microsoft.Agents.Teams.Application { /// /// Conversation update events. /// - public static class TeamsConversationUpdateEvents + public class TeamsConversationUpdateEvents : ConversationUpdateEvents { /// /// ChannelCreated event @@ -25,16 +27,6 @@ public static class TeamsConversationUpdateEvents /// public const string ChannelRestored = "channelRestored"; - /// - /// MembersAdded event - /// - public const string MembersAdded = "membersAdded"; - - /// - /// MembersRemoved event - /// - public const string MembersRemoved = "membersRemoved"; - /// /// TeamRenamed event /// diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index 92683f71..949a6c75 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -49,10 +49,12 @@ public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, AppSt /// public static async Task MessageHandlerAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) { - int count = turnState.Conversation.MessageCount; + //int count = turnState.Conversation.MessageCount; + int count = turnState.MessageCount(); // Increment count state. - turnState.Conversation.MessageCount = ++count; + //turnState.Conversation.MessageCount = ++count; + turnState.MessageCount(++count); await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); } diff --git a/src/samples/Application/messaging.echoBot/StateExtensions.cs b/src/samples/Application/messaging.echoBot/StateExtensions.cs new file mode 100644 index 00000000..761def4a --- /dev/null +++ b/src/samples/Application/messaging.echoBot/StateExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.Agents.BotBuilder.Application.State; +using System.Diagnostics.Metrics; + +namespace EchoBot +{ + public static class StateExtensions + { + public static int MessageCount(this TurnState state) + { + var value = state.GetValue("conversation.countKey"); + return value == null ? 0 : (int)value; + } + + public static void MessageCount(this TurnState state, int value) + { + state.SetValue("conversation.countKey", value); + } + } +} From f81a254de667061a63684a7073ca9f50d73dc843 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 4 Feb 2025 11:55:10 -0600 Subject: [PATCH 12/60] ITurnContext.TurnState -> ITurnState --- src/Microsoft.Agents.SDK.sln | 21 +- .../AdapterExtensions.cs | 59 -- .../ComponentDialog.cs | 3 +- .../Debugging/DebugSupport.cs | 3 +- .../Dialog.cs | 1 - .../DialogContext.cs | 20 +- .../DialogExtensions.cs | 19 +- .../DialogSet.cs | 3 +- .../DialogState.cs | 2 +- .../IRecognizer.cs | 1 - ...Microsoft.Agents.BotBuilder.Dialogs.csproj | 1 - .../Prompts/ActivityPrompt.cs | 1 - .../Prompts/AttachmentPrompt.cs | 1 - .../Prompts/ChoicePrompt.cs | 1 - .../Prompts/ConfirmPrompt.cs | 1 - .../Prompts/DatetimePrompt.cs | 1 - .../Prompts/NumberPrompt.cs | 1 - .../Prompts/OAuthPrompt.cs | 16 +- .../Prompts/Prompt.cs | 1 - .../Prompts/PromptValidatorContext.cs | 1 - .../Prompts/TextPrompt.cs | 1 - .../Recognizer.cs | 2 +- .../RegisterClassMiddleware.cs | 72 -- .../SkillDialog.cs | 15 +- .../SkillDialogOptions.cs | 2 +- .../WaterfallDialog.cs | 1 - .../AdaptiveCards/AdaptiveCards.cs | 2 +- .../AdaptiveCards/AdaptiveCardsHandlers.cs | 1 - .../Application/Application.cs | 3 +- .../Authentication/IAuthentication.cs | 1 - .../Application/HandoffHandler.cs | 1 - .../Application/Route.cs | 1 - .../Application/State/ITurnState.cs | 1 - .../Application/State/TurnState.cs | 1 - .../Application/StreamingResponse.cs | 1 - .../Application/TurnEventHandlerAsync.cs | 1 - .../Application/TypingTimer.cs | 1 - .../AssemblyInfo.cs | 1 + .../BotCallbackHandler.cs | 2 +- .../ChannelAdapter.cs | 5 +- .../ChannelServiceAdapterBase.cs | 28 +- .../Compat/ActivityHandler.cs | 3 +- .../Compat/AutoSaveStateMiddleware.cs | 96 ++ .../Compat/BotFrameworkSkillHandler.cs | 11 +- .../Compat/NormalizeMentionsMiddleware.cs | 1 - .../Compat/ShowTypingMiddleware.cs | 3 +- .../Compat}/TelemetryLoggerMiddleware.cs | 4 +- .../Compat}/TranscriptLoggerMiddleware.cs | 4 +- .../Compat}/TurnContextStateCollection.cs | 2 +- .../Compat/TypedTurnContext.cs | 7 +- .../Microsoft.Agents.BotBuilder/IBot.cs | 1 - .../IChannelAdapter.cs | 2 +- .../IMiddleware.cs | 4 +- .../IMiddlewareSet.cs | 2 +- .../ITurnContext.cs | 5 +- .../Microsoft.Agents.BotBuilder.csproj | 3 +- .../MiddlewareSet.cs | 1 - .../Microsoft.Agents.BotBuilder/OAuthFlow.cs | 7 +- .../State/BotState.cs | 501 +++++++++++ .../State/ConversationState.cs | 39 + .../State/IBotState.cs | 24 + .../State/IPropertyManager.cs | 22 + .../State/IStatePropertyAccessor.cs | 43 + .../State/IStatePropertyInfo.cs | 19 + .../State/ITurnState.cs | 29 + .../State/ObjectMerge.cs | 122 +++ .../State/ObjectPath.cs | 820 ++++++++++++++++++ .../State/PrivateConversationState.cs | 41 + .../State/TempState.cs | 83 ++ .../State/TurnState.cs | 166 ++++ .../State/UserState.cs | 39 + .../TurnContext.cs | 15 +- .../Models/HandoffEventFactory.cs | 19 +- .../HostedActivityService.cs | 1 - .../Hosting/AspNetCore/CloudAdapter.cs | 4 +- .../AspNetCore/ServiceCollectionExtensions.cs | 1 - .../Compat/SharePointActivityHandler.cs | 2 +- .../SharePointSSOTokenExchangeMiddleware.cs | 4 +- .../Application/ConfigHandlerAsync.cs | 4 +- .../Application/FeedbackLoopHandler.cs | 4 +- .../Application/FileConsentCardHandler.cs | 4 +- .../Application/IInputFileDownloader.cs | 2 +- .../Application/Meetings/MeetingsHandlers.cs | 4 +- .../MessageExtensions/MessageExtensions.cs | 23 +- .../MessageExtensionsHandlers.cs | 4 +- .../O365ConnectorCardActionHandler.cs | 4 +- .../Application/ReadReceiptHandler.cs | 4 +- .../Application/TaskModules/TaskModules.cs | 5 +- .../TaskModules/TaskModulesHandlers.cs | 4 +- .../Application/TeamsApplication.cs | 11 +- .../Compat/TeamsActivityHandler.cs | 2 +- .../Compat/TeamsSSOTokenExchangeMiddleware.cs | 4 +- .../Connector/TeamsInfo.cs | 4 +- .../FileTranscriptLogger.cs | 1 - .../messaging.echoBot/EchoBotApplication.cs | 2 +- src/samples/AuthenticationBot/AuthBot.cs | 3 +- .../AuthenticationBot.csproj | 1 - src/samples/AuthenticationBot/Program.cs | 5 +- .../Bot1/BotHostAdapterWithErrorHandler.cs | 3 +- src/samples/BotToBot/Bot1/Bots/Bot1.cs | 3 +- .../Bot2/BotAdapterWithErrorHandler.cs | 3 +- src/samples/BotToBot/Bot2/Bots/Bot2.cs | 3 +- .../BotAdapterWithErrorHandler.cs | 3 +- .../CopilotStudioBot.cs | 2 +- src/samples/EchoBot/MyBot.cs | 2 +- .../SemanticKernel/WeatherBot/MyBot.cs | 3 +- .../Bots/AdaptiveCardActionsBot.cs | 2 +- .../Bots/TeamsConversationBot.cs | 2 +- .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 2 +- .../Bots/InMeetingNotifications.cs | 2 +- .../Bots/TeamsMessagingExtensionsSearchBot.cs | 2 +- .../TaskModule/Bots/TeamsTaskModuleBot.cs | 2 +- .../Teams/bot-all-cards/Bots/DialogBot.cs | 4 +- .../Teams/bot-all-cards/Bots/TeamsBot.cs | 4 +- src/samples/Teams/bot-all-cards/Program.cs | 2 +- .../Bots/DialogBot.cs | 4 +- .../Bots/TeamsBot.cs | 4 +- .../Dialogs/LogoutDialog.cs | 2 +- .../Program.cs | 5 +- .../TeamsSSOAdapter.cs | 5 +- .../Bots/ActivityBot.cs | 2 +- .../Program.cs | 2 +- .../Bots/ActivityBot.cs | 2 +- .../Program.cs | 2 +- .../Teams/bot-tag-mention/Bots/DialogBot.cs | 4 +- .../Bots/TeamsTagMentionBot.cs | 4 +- .../bot-tag-mention/Dialogs/LogoutDialog.cs | 2 +- src/samples/Teams/bot-tag-mention/Program.cs | 4 +- .../Bots/DialogBot.cs | 4 +- .../bot-teams-authentication/Bots/TeamsBot.cs | 4 +- .../Dialogs/LogoutDialog.cs | 2 +- .../Teams/bot-teams-authentication/Program.cs | 2 +- .../Adapters/TestAdapter.cs | 3 +- .../BotBuilder.Testing/Adapters/TestFlow.cs | 1 - .../BotBuilder.Testing/DialogTestClient.cs | 4 +- .../BotBuilder.Testing/TestAdapterTests.cs | 1 - .../XUnit/XUnitDialogTestLogger.cs | 5 +- .../ActivityPromptTests.cs | 4 +- .../AttachmentPromptTests.cs | 3 +- .../ChoicePromptTests.cs | 4 +- .../ChoicesChannelTests.cs | 2 - .../CloudOAuthPromptTests.cs | 3 +- .../ComponentDialogTests.cs | 4 +- .../ConfirmPromptLocTests.cs | 3 +- .../ConfirmPromptTests.cs | 3 +- .../DateTimePromptTests.cs | 3 +- .../DialogExtensionsTests.cs | 15 +- .../DialogSetTests.cs | 2 +- .../NumberPromptTests.cs | 3 +- .../OAuthPromptTests.cs | 5 +- .../PromptValidatorContextTests.cs | 4 +- .../ReplaceDialogTest.cs | 4 +- .../SkillDialogTests.cs | 4 +- .../TestCloudAdapter.cs | 4 +- .../TextPromptTests.cs | 3 +- .../WaterfallTests.cs | 3 +- .../Bots/DialogAndWelcomeBot.cs | 3 +- .../Bots/DialogBot.cs | 3 +- .../Bots/MyBot.cs | 2 - .../Bots/ProactiveBot.cs | 3 +- .../DialogExtensions.cs | 3 +- .../Dialogs/UserProfileDialog.cs | 2 +- .../Bots/DialogAndWelcomeBotTests.cs | 2 +- .../Bots/DialogBotTests.cs | 2 +- .../Framework/SimpleMockFactory.cs | 1 - .../ActivityHandlerTests.cs | 4 +- .../AnonymousReceiveMiddleware.cs | 1 - .../CallCountingMiddleware.cs | 1 - .../ChannelAdapterBracketingTest.cs | 1 - .../ChannelAdapterTests.cs | 1 - .../MiddlewareSetTests.cs | 2 - .../NotImplementedAdapter.cs | 4 +- .../SharePointActivityHandlerTests.cs | 1 - .../ShowTypingMiddlewareTests.cs | 2 +- .../SimpleAdapter.cs | 1 - .../TurnContextTests.cs | 17 +- .../BotFrameworkSkillHandlerTests.cs | 12 +- .../HostedActivityServiceTests.cs | 2 +- .../CloudAdapterTests.cs | 9 +- .../ServiceCollectionExtensionsTests.cs | 1 - .../AutoSaveStateMiddlewareTests.cs | 4 +- .../BotStateSetTests.cs | 22 +- .../BotStateTests.cs | 11 +- .../Microsoft.Agents.State.Tests.csproj | 1 - .../ObjectPathTests.cs | 2 +- .../TranscriptLoggerMiddlewareTests.cs | 2 + .../NotImplementedAdapter.cs | 1 - .../SimpleAdapter.cs | 1 - .../TestActivityHandler.cs | 2 +- 189 files changed, 2337 insertions(+), 522 deletions(-) delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/AdapterExtensions.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/RegisterClassMiddleware.cs rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder}/BotCallbackHandler.cs (93%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs rename src/libraries/{Core/Microsoft.Agents.Telemetry => BotBuilder/Microsoft.Agents.BotBuilder/Compat}/TelemetryLoggerMiddleware.cs (99%) rename src/libraries/{Storage/Microsoft.Agents.Storage.Transcript => BotBuilder/Microsoft.Agents.BotBuilder/Compat}/TranscriptLoggerMiddleware.cs (98%) rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder/Compat}/TurnContextStateCollection.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder}/IChannelAdapter.cs (99%) rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder}/IMiddleware.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder}/IMiddlewareSet.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Interfaces => BotBuilder/Microsoft.Agents.BotBuilder}/ITurnContext.cs (99%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IPropertyManager.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyAccessor.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyInfo.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectMerge.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectPath.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TurnState.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index b7e48368..742e8df5 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -90,8 +90,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherBot", "samples\Seman EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Authentication", "libraries\Core\Microsoft.Agents.Authentication\Microsoft.Agents.Authentication.csproj", "{D3904B56-91D6-4E87-9422-3337C1319C7F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Telemetry", "libraries\Core\Microsoft.Agents.Telemetry\Microsoft.Agents.Telemetry.csproj", "{30378105-38A9-465D-BD78-63B65C389E61}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder", "libraries\BotBuilder\Microsoft.Agents.BotBuilder\Microsoft.Agents.BotBuilder.csproj", "{379985EF-21A2-49A8-8882-F797CC015F10}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder.Dialogs", "libraries\BotBuilder\Microsoft.Agents.BotBuilder.Dialogs\Microsoft.Agents.BotBuilder.Dialogs.csproj", "{B69076E7-D54D-4CC1-9DCB-43F5270BABAB}" @@ -118,8 +116,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.State.Tests", "tests\Microsoft.Agents.State.Tests\Microsoft.Agents.State.Tests.csproj", "{71813B2D-D6A8-4388-9541-9B1287C93F19}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.State", "libraries\Core\Microsoft.Agents.State\Microsoft.Agents.State.csproj", "{6A27C156-842F-409C-86B7-D9CC0A38F02F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvalClient", "samples\EvalClient\EvalClient.csproj", "{A839D635-0382-4E4C-8052-1F18B71434EE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Hosting.AspNetCore.Tests", "tests\Microsoft.Agents.Hosting.AspNetCore\Microsoft.Agents.Hosting.AspNetCore.Tests.csproj", "{7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}" @@ -138,6 +134,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Teams", "l EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.SharePoint", "libraries\Partner\Microsoft.Agents.SharePoint\Microsoft.Agents.SharePoint.csproj", "{20C15862-9867-4929-8E7C-D46FAD6AAF40}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Telemetry", "libraries\Core\Microsoft.Agents.Telemetry\Microsoft.Agents.Telemetry.csproj", "{5C77CD80-5A4E-4410-8A75-C608D885E5CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -268,10 +266,6 @@ Global {D3904B56-91D6-4E87-9422-3337C1319C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3904B56-91D6-4E87-9422-3337C1319C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3904B56-91D6-4E87-9422-3337C1319C7F}.Release|Any CPU.Build.0 = Release|Any CPU - {30378105-38A9-465D-BD78-63B65C389E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30378105-38A9-465D-BD78-63B65C389E61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30378105-38A9-465D-BD78-63B65C389E61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30378105-38A9-465D-BD78-63B65C389E61}.Release|Any CPU.Build.0 = Release|Any CPU {379985EF-21A2-49A8-8882-F797CC015F10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {379985EF-21A2-49A8-8882-F797CC015F10}.Debug|Any CPU.Build.0 = Debug|Any CPU {379985EF-21A2-49A8-8882-F797CC015F10}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -320,10 +314,6 @@ Global {71813B2D-D6A8-4388-9541-9B1287C93F19}.Debug|Any CPU.Build.0 = Debug|Any CPU {71813B2D-D6A8-4388-9541-9B1287C93F19}.Release|Any CPU.ActiveCfg = Release|Any CPU {71813B2D-D6A8-4388-9541-9B1287C93F19}.Release|Any CPU.Build.0 = Release|Any CPU - {6A27C156-842F-409C-86B7-D9CC0A38F02F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A27C156-842F-409C-86B7-D9CC0A38F02F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A27C156-842F-409C-86B7-D9CC0A38F02F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A27C156-842F-409C-86B7-D9CC0A38F02F}.Release|Any CPU.Build.0 = Release|Any CPU {A839D635-0382-4E4C-8052-1F18B71434EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A839D635-0382-4E4C-8052-1F18B71434EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {A839D635-0382-4E4C-8052-1F18B71434EE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -354,6 +344,10 @@ Global {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Debug|Any CPU.Build.0 = Debug|Any CPU {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Release|Any CPU.ActiveCfg = Release|Any CPU {20C15862-9867-4929-8E7C-D46FAD6AAF40}.Release|Any CPU.Build.0 = Release|Any CPU + {5C77CD80-5A4E-4410-8A75-C608D885E5CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C77CD80-5A4E-4410-8A75-C608D885E5CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C77CD80-5A4E-4410-8A75-C608D885E5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C77CD80-5A4E-4410-8A75-C608D885E5CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -398,7 +392,6 @@ Global {5059670B-CB2F-4D97-8B24-872FD1EC9D99} = {674A812C-7287-4883-97F9-697D83750648} {1889C7A6-B006-4F47-8578-73D95236E94C} = {5059670B-CB2F-4D97-8B24-872FD1EC9D99} {D3904B56-91D6-4E87-9422-3337C1319C7F} = {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} - {30378105-38A9-465D-BD78-63B65C389E61} = {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} {379985EF-21A2-49A8-8882-F797CC015F10} = {F81B3CA1-5DB1-45E9-95B7-3B774588E635} {B69076E7-D54D-4CC1-9DCB-43F5270BABAB} = {F81B3CA1-5DB1-45E9-95B7-3B774588E635} {4B844752-C6D6-4268-B918-5C6EDE3D1A61} = {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} @@ -412,7 +405,6 @@ Global {23065AEC-4875-4CDC-A7E5-EE389C201534} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {401F5548-D6EF-4497-B786-D2A6BA8CBF76} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {71813B2D-D6A8-4388-9541-9B1287C93F19} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {6A27C156-842F-409C-86B7-D9CC0A38F02F} = {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} {A839D635-0382-4E4C-8052-1F18B71434EE} = {674A812C-7287-4883-97F9-697D83750648} {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {06E490F7-F0BB-E3C4-54FE-5210627292A1} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} @@ -422,6 +414,7 @@ Global {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {81F8DD27-1795-4D80-9C7D-681CC495B38F} = {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} {20C15862-9867-4929-8E7C-D46FAD6AAF40} = {8BA20AC9-A0C4-4386-AF9A-E48D870E965D} + {5C77CD80-5A4E-4410-8A75-C608D885E5CD} = {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/AdapterExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/AdapterExtensions.cs deleted file mode 100644 index d0648490..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/AdapterExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Storage; - -namespace Microsoft.Agents.BotBuilder.Dialogs -{ - /// - /// Defines extension methods for the class. - /// - public static class AdapterExtensions - { - /// - /// Adds middleware to the adapter to register an object on the turn context. - /// The middleware registers the state objects on the turn context at the start of each turn. - /// - /// The adapter on which to register the storage object. - /// The storage object to register. - /// The updated adapter. - /// - /// To get the storage object, use the turn context's - /// property's method. - /// - public static IChannelAdapter UseStorage(this IChannelAdapter ChannelAdapter, IStorage storage) - { - return ChannelAdapter.Use(new RegisterClassMiddleware(storage ?? throw new ArgumentNullException(nameof(storage)))); - } - - /// - /// Adds middleware to the adapter to register one or more objects on the turn context. - /// The middleware registers the state objects on the turn context at the start of each turn. - /// - /// The adapter on which to register the state objects. - /// The state objects to register. - /// The updated adapter. - /// - /// To get the state objects, use the turn context's - /// property's method, where the `key` - /// parameter is the fully qualified name of the type of bot state to get. - /// - public static IChannelAdapter UseBotState(this IChannelAdapter ChannelAdapter, params BotState[] botStates) - { - if (botStates == null) - { - throw new ArgumentNullException(nameof(botStates)); - } - - foreach (var botState in botStates) - { - ChannelAdapter.Use(new RegisterClassMiddleware(botState, botState.GetType().FullName)); - } - - return ChannelAdapter; - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/ComponentDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/ComponentDialog.cs index f87c058e..56864921 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/ComponentDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/ComponentDialog.cs @@ -1,13 +1,12 @@ // Licensed under the MIT License. // Copyright (c) Microsoft Corporation. All rights reserved. -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Telemetry; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.Telemetry; namespace Microsoft.Agents.BotBuilder.Dialogs { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs index 1617006e..7ba68560 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; @@ -24,7 +23,7 @@ public static class DebugSupport /// turnContext. /// IDialogDebugger. public static IDialogDebugger GetDebugger(this ITurnContext context) => - context.TurnState.Get() ?? NullDialogDebugger.Instance; + context.TurnState.Temp.GetValue() ?? NullDialogDebugger.Instance; /// /// Extension method to get IDialogDebugger from DialogContext. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Dialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Dialog.cs index 7c464c8c..6feed6dd 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Dialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Dialog.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Debugging; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Telemetry; namespace Microsoft.Agents.BotBuilder.Dialogs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs index db12eca2..f5fd36ec 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Dialogs.Debugging; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.State; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.Dialogs { @@ -32,7 +32,6 @@ public DialogContext(DialogSet dialogs, ITurnContext turnContext, DialogState st Dialogs = dialogs ?? throw new ArgumentNullException(nameof(dialogs)); Context = turnContext ?? throw new ArgumentNullException(nameof(turnContext)); Stack = state.DialogStack; - Services = new TurnContextStateCollection(); } /// @@ -48,15 +47,6 @@ public DialogContext( : this(dialogs, parentDialogContext.Context, state) { Parent = parentDialogContext ?? throw new ArgumentNullException(nameof(parentDialogContext)); - - if (Parent.Services != null) - { - // copy parent services into this dialogcontext. - foreach (var service in Parent.Services) - { - Services[service.Key] = service.Value; - } - } } /// @@ -252,9 +242,9 @@ public DialogInstance ActiveDialog { // if we are continuing and haven't emitted the activityReceived event, emit it // NOTE: This is backward compatible way for activity received to be fired even if you have legacy dialog loop - if (!this.Context.TurnState.ContainsKey("activityReceivedEmitted")) + if (!Context.TurnState.Temp.HasValue("activityReceivedEmitted")) { - this.Context.TurnState["activityReceivedEmitted"] = true; + Context.TurnState.Temp.SetValue("activityReceivedEmitted", true); // Dispatch "activityReceived" event // - This will queue up any interruptions. @@ -484,7 +474,7 @@ public DialogInstance ActiveDialog // End the current dialog and giving the reason. await EndActiveDialogAsync(DialogReason.ReplaceCalled, cancellationToken: cancellationToken).ConfigureAwait(false); - ObjectPath.SetPathValue(this.Context.TurnState, "turn.__repeatDialogId", dialogId); + Context.TurnState.Temp.SetValue("turn.__repeatDialogId", dialogId); // Start replacement dialog return await BeginDialogAsync(dialogId, options, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index d2f09215..15787890 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -11,10 +11,9 @@ using Microsoft.Agents.Client; using Microsoft.Agents.Authentication; using Microsoft.Agents.Telemetry; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.Dialogs { @@ -42,7 +41,7 @@ public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, var dialogSet = new DialogSet(dialogState); // look for the IBotTelemetryClient on the TurnState, if not there take it from the Dialog, if not there fall back to the "null" default - dialogSet.TelemetryClient = turnContext.TurnState.Get() ?? dialog.TelemetryClient ?? NullBotTelemetryClient.Instance; + dialogSet.TelemetryClient = turnContext.TurnState.Temp.GetValue() ?? dialog.TelemetryClient ?? NullBotTelemetryClient.Instance; dialogSet.Add(dialog); @@ -53,12 +52,6 @@ public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, internal static async Task InternalRunAsync(ITurnContext turnContext, string dialogId, DialogContext dialogContext, CancellationToken cancellationToken) { - // map TurnState into root dialog context.services - foreach (var service in turnContext.TurnState) - { - dialogContext.Services[service.Key] = service.Value; - } - DialogTurnResult dialogTurnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. @@ -171,11 +164,11 @@ private static async Task InnerRunAsync(ITurnContext turnConte /// private static bool SendEoCToParent(ITurnContext turnContext) { - if (turnContext.TurnState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims)) + if (turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims)) { // EoC Activities returned by skills are bounced back to the bot by SkillHandler. // In those cases we will have a SkillConversationReference instance in state. - var skillConversationReference = turnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey); + var skillConversationReference = turnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey); if (skillConversationReference != null) { // If the skillConversationReference.OAuthScope is for one of the supported channels, we are at the root and we should not send an EoC. @@ -190,12 +183,12 @@ private static bool SendEoCToParent(ITurnContext turnContext) private static bool IsFromParentToSkill(ITurnContext turnContext) { - if (turnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey) != null) + if (turnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey) != null) { return false; } - return turnContext.TurnState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); + return turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); } // Recursively walk up the DC stack to find the active DC. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogSet.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogSet.cs index 93750ebb..a77aa31c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogSet.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogSet.cs @@ -7,8 +7,7 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Telemetry; namespace Microsoft.Agents.BotBuilder.Dialogs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogState.cs index 2bad4b9c..0db76f26 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogState.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; +using Microsoft.Agents.BotBuilder.State; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/IRecognizer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/IRecognizer.cs index ddbb816a..a586ba8e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/IRecognizer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/IRecognizer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Microsoft.Agents.BotBuilder.Dialogs.csproj b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Microsoft.Agents.BotBuilder.Dialogs.csproj index 3e03d8bf..d73ace42 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Microsoft.Agents.BotBuilder.Dialogs.csproj +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Microsoft.Agents.BotBuilder.Dialogs.csproj @@ -21,7 +21,6 @@ - diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs index 8ad7dd90..753e768a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/AttachmentPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/AttachmentPrompt.cs index 535df2a2..b8fc7b9d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/AttachmentPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/AttachmentPrompt.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ChoicePrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ChoicePrompt.cs index 1969a6cf..8035a6cc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ChoicePrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ChoicePrompt.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using static Microsoft.Agents.BotBuilder.Dialogs.Prompts.PromptCultureModels; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ConfirmPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ConfirmPrompt.cs index 429d3437..0395c761 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ConfirmPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ConfirmPrompt.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Recognizers.Text.Choice; using static Microsoft.Agents.BotBuilder.Dialogs.Prompts.PromptCultureModels; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/DatetimePrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/DatetimePrompt.cs index 162779d1..c1550d0e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/DatetimePrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/DatetimePrompt.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Recognizers.Text.DateTime; using static Microsoft.Recognizers.Text.Culture; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/NumberPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/NumberPrompt.cs index 1a5dd1d3..19d2fc55 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/NumberPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/NumberPrompt.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Recognizers.Text; using Microsoft.Recognizers.Text.Number; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 1be2ba9d..eb87ee7e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.Agents.Authentication; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; namespace Microsoft.Agents.BotBuilder.Dialogs @@ -198,17 +197,10 @@ public override async Task ContinueDialogAsync(DialogContext d // recreate a ConnectorClient and set it in TurnState so replies use the correct one var serviceUrl = dc.Context.Activity.ServiceUrl; - var claimsIdentity = dc.Context.TurnState.Get(ChannelAdapter.BotIdentityKey); + var claimsIdentity = dc.Context.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey); var audience = callerInfo.Scope; var connectorClient = await CreateConnectorClientAsync(dc.Context, serviceUrl, claimsIdentity, audience, cancellationToken).ConfigureAwait(false); - if (dc.Context.TurnState.Get() != null) - { - dc.Context.TurnState.Set(connectorClient); - } - else - { - dc.Context.TurnState.Add(connectorClient); - } + dc.Context.TurnState.Temp.SetValue(connectorClient); } } @@ -286,7 +278,7 @@ public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken c public static async Task CreateConnectorClientAsync(ITurnContext turnContext, string serviceUrl, ClaimsIdentity claimsIdentity, string audience, CancellationToken cancellationToken) { - var clientFactory = turnContext.TurnState.Get(); + var clientFactory = turnContext.TurnState.Temp.GetValue(); if (clientFactory != null) { return await clientFactory.CreateConnectorClientAsync(claimsIdentity, serviceUrl, audience, cancellationToken).ConfigureAwait(false); @@ -302,7 +294,7 @@ private static bool IsTokenResponseEvent(ITurnContext turnContext) private static CallerInfo CreateCallerInfo(ITurnContext turnContext) { - if (turnContext.TurnState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && BotClaims.IsBotClaim(botIdentity.Claims)) + if (turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && BotClaims.IsBotClaim(botIdentity.Claims)) { return new CallerInfo { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs index 2e1f7d69..64d8e6a0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs index b2c2fe7d..99da174f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System.Collections.Generic; namespace Microsoft.Agents.BotBuilder.Dialogs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/TextPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/TextPrompt.cs index 19326681..223c9faa 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/TextPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/TextPrompt.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs index 3d392835..d430448a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs @@ -212,7 +212,7 @@ protected void TrackRecognizerResult(DialogContext dialogContext, string eventNa { if (TelemetryClient is NullBotTelemetryClient) { - var turnStateTelemetryClient = dialogContext.Context.TurnState.Get(); + var turnStateTelemetryClient = dialogContext.Context.TurnState.Temp.GetValue(); TelemetryClient = turnStateTelemetryClient ?? TelemetryClient; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/RegisterClassMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/RegisterClassMiddleware.cs deleted file mode 100644 index 14330200..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/RegisterClassMiddleware.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.BotBuilder.Dialogs -{ - /// - /// Middleware for adding an object to or registering a service with the current turn context. - /// - /// The type of object or service to add. - public class RegisterClassMiddleware : IMiddleware - where T : class - { - private readonly string _key; - - /// - /// Initializes a new instance of the class. - /// - /// The object or service to add. - public RegisterClassMiddleware(T service) - { - this.Service = service; - } - - /// - /// Initializes a new instance of the class. - /// - /// The object or service to add. - /// optional key for service object in turn state (default is instance.GetType().FullName). - public RegisterClassMiddleware(T service, string key) - { - this.Service = service; - this._key = key; - } - - /// - /// Gets or sets the object or service to add to the turn context. - /// - /// - /// The object or service to add. - /// - public T Service { get; set; } - - /// - /// Adds the associated object or service to the current turn context. - /// - /// The context object for this turn. - /// The delegate to call to continue the bot middleware pipeline. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// - /// - public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken) - { - // Register service - if (this._key != null) - { - turnContext.TurnState.Add(this._key, this.Service); - } - else - { - turnContext.TurnState.Add(this.Service); - } - - await nextTurn(cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs index 0affd93c..977bf5e8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Client; +using Microsoft.Agents.Connector; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using System; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Connector; -using Microsoft.Agents.Client; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.State; namespace Microsoft.Agents.BotBuilder.Dialogs { @@ -311,7 +310,7 @@ private async Task SendToSkillAsync(ITurnContext context, IActivity a /// private async Task InterceptOAuthCardsAsync(ITurnContext turnContext, IActivity activity, string connectionName, CancellationToken cancellationToken) { - var userTokenClient = turnContext.TurnState.Get(); + var userTokenClient = turnContext.TurnState.Temp.GetValue(); if (string.IsNullOrWhiteSpace(connectionName) || userTokenClient == null) { // The adapter may choose not to support token exchange, in which case we fallback to showing an oauth card to the user. @@ -378,7 +377,7 @@ private async Task CreateSkillConversationIdAsync(ITurnContext context, // Create a conversationId to interact with the skill and send the activity var conversationIdFactoryOptions = new ConversationIdFactoryOptions { - FromBotOAuthScope = context.TurnState.Get(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = context.TurnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey), FromBotId = DialogOptions.BotId, Activity = activity, Bot = DialogOptions.Skill diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialogOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialogOptions.cs index 9ed180f9..22bc43f6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialogOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialogOptions.cs @@ -3,7 +3,7 @@ using System; using System.Text.Json.Serialization; -using Microsoft.Agents.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Client; namespace Microsoft.Agents.BotBuilder.Dialogs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs index 8c3d10a3..d081432e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Telemetry; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs index 611df725..8ac54344 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs @@ -347,7 +347,7 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand IList results = await handler(turnContext, turnState, query, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { SearchInvokeResponse searchInvokeResponse = new() { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs index 2732435c..9c3ea931 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs index e7856795..08ac5fa8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs @@ -3,7 +3,6 @@ using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System; @@ -447,7 +446,7 @@ public Application OnHandoff(HandoffHandler handler) await handler(turnContext, turnState, token, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs index 1863fb67..e6ee1311 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs index 59d23ea1..a3cecfbe 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs index df49b445..44c00ce5 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs index 7a897fb3..4b88b75f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Storage; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs index 22f06abc..3ecc4327 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Storage; using Newtonsoft.Json.Linq; using System; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs index b9508195..eb8f3840 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs index c2e6928c..d4c38b17 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs index 8eb3b110..aedf2f55 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs index 42c714bb..bd758961 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs @@ -7,3 +7,4 @@ [assembly: InternalsVisibleTo("Microsoft.Agents.BotBuilder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.Agents.Connector.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.Agents.Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.Agents.State.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/BotCallbackHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotCallbackHandler.cs similarity index 93% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/BotCallbackHandler.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotCallbackHandler.cs index 7cced52a..b1950a7f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/BotCallbackHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/BotCallbackHandler.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder { /// /// The callback delegate for application code. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs index 067dfbd4..7794cd79 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs @@ -5,7 +5,6 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -21,7 +20,7 @@ public abstract class ChannelAdapter(ILogger logger = null) : IChannelAdapter /// /// The key value for any InvokeResponseActivity that would be on the TurnState. /// - public const string InvokeResponseKey = "ChannelAdapter.InvokeResponse"; + public const string InvokeResponseKey = "ChannelAdapterInvokeResponse"; /// /// The string value for the bot identity key. @@ -31,7 +30,7 @@ public abstract class ChannelAdapter(ILogger logger = null) : IChannelAdapter /// /// The string value for the OAuth scope key. /// - public const string OAuthScopeKey = "Microsoft.Agents.Protocols.Adapter.ChannelAdapter.OAuthScope"; + public const string OAuthScopeKey = "ChannelAdapterOAuthScope"; /// /// Logger for the bot adapter. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index 36a3bbf8..5415ee27 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -10,9 +10,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Connector; using Microsoft.Agents.Connector.Types; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -23,9 +23,11 @@ namespace Microsoft.Agents.BotBuilder /// An adapter that implements the Activity Protocol and can be hosted in different cloud environments both public and private. /// /// The IConnectorFactory to use. + /// /// The ILogger implementation this adapter should use. public abstract class ChannelServiceAdapterBase( IChannelServiceClientFactory channelServiceClientFactory, + ITurnState state = null, ILogger logger = null) : ChannelAdapter(logger) { /// @@ -44,6 +46,8 @@ public abstract class ChannelServiceAdapterBase( /// protected ILogger Logger { get; private set; } = logger ?? NullLogger.Instance; + private ITurnState State = state ?? new TurnState(); + /// public override async Task SendActivitiesAsync(ITurnContext turnContext, IActivity[] activities, CancellationToken cancellationToken) { @@ -75,7 +79,7 @@ public override async Task SendActivitiesAsync(ITurnContext } else if (activity.Type == ActivityTypes.InvokeResponse) { - turnContext.TurnState.Add(InvokeResponseKey, activity); + turnContext.TurnState.Temp.SetValue(InvokeResponseKey, activity); } else if (activity.Type == ActivityTypes.Trace && activity.ChannelId != Channels.Emulator) { @@ -85,12 +89,12 @@ public override async Task SendActivitiesAsync(ITurnContext { if (!string.IsNullOrWhiteSpace(activity.ReplyToId)) { - var connectorClient = turnContext.TurnState.Get(); + var connectorClient = turnContext.TurnState.Temp.GetValue(); response = await connectorClient.Conversations.ReplyToActivityAsync(activity, cancellationToken).ConfigureAwait(false); } else { - var connectorClient = turnContext.TurnState.Get(); + var connectorClient = turnContext.TurnState.Temp.GetValue(); response = await connectorClient.Conversations.SendToConversationAsync(activity, cancellationToken).ConfigureAwait(false); } } @@ -111,7 +115,7 @@ public override async Task UpdateActivityAsync(ITurnContext tu Logger.LogInformation($"UpdateActivityAsync ActivityId: {activity.Id}"); - var connectorClient = turnContext.TurnState.Get(); + var connectorClient = turnContext.TurnState.Temp.GetValue(); return await connectorClient.Conversations.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); } @@ -123,7 +127,7 @@ public override async Task DeleteActivityAsync(ITurnContext turnContext, Convers Logger.LogInformation($"DeleteActivityAsync Conversation Id: {reference.Conversation.Id}, ActivityId: {reference.ActivityId}"); - var connectorClient = turnContext.TurnState.Get(); + var connectorClient = turnContext.TurnState.Temp.GetValue(); await connectorClient.Conversations.DeleteActivityAsync(reference.Conversation.Id, reference.ActivityId, cancellationToken).ConfigureAwait(false); } @@ -327,11 +331,11 @@ private static Activity CreateCreateActivity(ConversationResourceResponse create private TurnContext CreateTurnContext(IActivity activity, ClaimsIdentity claimsIdentity, string oauthScope, IConnectorClient connectorClient, IUserTokenClient userTokenClient, BotCallbackHandler callback) { - var turnContext = new TurnContext(this, activity); - turnContext.TurnState.Add(BotIdentityKey, claimsIdentity); - turnContext.TurnState.Add(connectorClient); - turnContext.TurnState.Add(userTokenClient); - turnContext.TurnState.Set(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set + var turnContext = new TurnContext(this, activity, State); + //turnContext.TurnState.Temp.SetValue(BotIdentityKey, claimsIdentity); + turnContext.TurnState.Temp.SetValue(connectorClient); + turnContext.TurnState.Temp.SetValue(userTokenClient); + turnContext.TurnState.Temp.SetValue(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set return turnContext; } @@ -354,7 +358,7 @@ private static InvokeResponse ProcessTurnResults(TurnContext turnContext) // Handle Invoke scenarios where the Bot will return a specific body and return code. if (turnContext.Activity.Type == ActivityTypes.Invoke) { - var activityInvokeResponse = turnContext.TurnState.Get(InvokeResponseKey); + var activityInvokeResponse = turnContext.TurnState.Temp.GetValue(InvokeResponseKey); if (activityInvokeResponse == null) { return new InvokeResponse { Status = (int)HttpStatusCode.NotImplemented }; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs index 391ee1c6..4c907a71 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; @@ -96,7 +95,7 @@ public virtual async Task OnTurnAsync(ITurnContext turnContext, CancellationToke var invokeResponse = await OnInvokeActivityAsync(new TypedTurnContext(turnContext), cancellationToken).ConfigureAwait(false); // If OnInvokeActivityAsync has already sent an InvokeResponse, do not send another one. - if (invokeResponse != null && turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (invokeResponse != null && turnContext.TurnState.Temp.GetValue(ChannelAdapter.InvokeResponseKey) == null) { await turnContext.SendActivityAsync(new Activity { Value = invokeResponse, Type = ActivityTypes.InvokeResponse }, cancellationToken).ConfigureAwait(false); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs new file mode 100644 index 00000000..71e7e88f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Agents.BotBuilder.State; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.Compat +{ + /// + /// Middleware to automatically persist state before the end of each turn. + /// + /// + /// This calls + /// on each state object it manages. + /// + public class AutoSaveStateMiddleware : IMiddleware + { + private readonly bool _autoLoad; + + /// + /// Initializes a new instance of the class. + /// + /// initial list of objects to manage. + public AutoSaveStateMiddleware(params BotState[] botStates) + { + _autoLoad = false; + BotStateSet = new TurnState(botStates); + } + + /// + /// Allows for optionally auto-loading BotState at turn start. + /// + /// + /// + public AutoSaveStateMiddleware(bool autoLoad, params BotState[] botStates) + { + _autoLoad = autoLoad; + BotStateSet = new TurnState(botStates); + } + + /// + /// Initializes a new instance of the class with + /// a list of state management objects managed by this object. + /// + /// The state management objects managed by this object. + public AutoSaveStateMiddleware(TurnState botStateSet) + { + BotStateSet = botStateSet; + } + + /// + /// Gets or sets the list of state management objects managed by this object. + /// + /// The state management objects managed by this object. + public TurnState BotStateSet { get; set; } + + /// + /// Adds a state management object to the list of states to manage. + /// + /// The bot state to add. + /// The updated object. + public AutoSaveStateMiddleware Add(BotState botState) + { + ArgumentNullException.ThrowIfNull(botState); + + BotStateSet.Add(botState); + return this; + } + + /// + /// Before the turn ends, calls + /// on each state object. + /// + /// The context object for this turn. + /// The delegate to call to continue the bot middleware pipeline. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// This middleware persists state after the bot logic completes and before the turn ends. + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) + { + // before turn + if (_autoLoad) + { + await BotStateSet.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); + } + + await next(cancellationToken).ConfigureAwait(false); + + // after turn + await BotStateSet.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs index 6dc30a4d..6beb3f4a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs @@ -12,7 +12,6 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Connector; @@ -79,7 +78,7 @@ public async Task OnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string co var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference); + turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); await turnContext.DeleteActivityAsync(activityId, cancellationToken).ConfigureAwait(false); }); @@ -93,7 +92,7 @@ public async Task OnUpdateActivityAsync(ClaimsIdentity claimsI ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference); + turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = activityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; @@ -112,7 +111,7 @@ public async Task OnGetMemberAsync(ClaimsIdentity claimsIdentity var callback = new BotCallbackHandler(async (turnContext, ct) => { - var client = turnContext.TurnState.Get(); + var client = turnContext.TurnState.Temp.GetValue(); var conversationId = turnContext.Activity.Conversation.Id; member = await client.Conversations.GetConversationMemberAsync(userId, conversationId, cancellationToken).ConfigureAwait(false); }); @@ -149,7 +148,7 @@ public async Task OnGetConversationMemberAsync(ClaimsIdentity cl var callback = new BotCallbackHandler(async (turnContext, ct) => { - var client = turnContext.TurnState.Get(); + var client = turnContext.TurnState.Temp.GetValue(); var conversationId = turnContext.Activity.Conversation.Id; member = await client.Conversations.GetConversationMemberAsync(userId, conversationId, cancellationToken).ConfigureAwait(false); }); @@ -220,7 +219,7 @@ private async Task ProcessActivityAsync(ClaimsIdentity claimsI var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference); + turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs index ff2e22ed..3ad50454 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Linq; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index c4702cfd..840f0222 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.Authentication; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Concurrent; @@ -83,7 +82,7 @@ public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, Cance private static bool IsSkillBot(ITurnContext turnContext) { - return turnContext.TurnState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity + return turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); } diff --git a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TelemetryLoggerMiddleware.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TelemetryLoggerMiddleware.cs index 96a58cf3..f882e09b 100644 --- a/src/libraries/Core/Microsoft.Agents.Telemetry/TelemetryLoggerMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TelemetryLoggerMiddleware.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Telemetry; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Telemetry +namespace Microsoft.Agents.BotBuilder.Compat { /// /// Uses a object to log incoming, outgoing, updated, or deleted message activities. diff --git a/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/TranscriptLoggerMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TranscriptLoggerMiddleware.cs similarity index 98% rename from src/libraries/Storage/Microsoft.Agents.Storage.Transcript/TranscriptLoggerMiddleware.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TranscriptLoggerMiddleware.cs index e02917c5..76776b20 100644 --- a/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/TranscriptLoggerMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TranscriptLoggerMiddleware.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Storage.Transcript; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Storage.Transcript +namespace Microsoft.Agents.BotBuilder.Compat { /// /// Middleware for logging incoming and outgoing activities to an . diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/TurnContextStateCollection.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/TurnContextStateCollection.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs index 679502ec..c32430e1 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/TurnContextStateCollection.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder.Compat { /// /// Values persisted for the lifetime of the turn as part of the . diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs index 62ad9d1a..6b603f2d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System.Runtime.CompilerServices; using System.Threading; @@ -29,8 +28,6 @@ internal class TypedTurnContext(ITurnContext innerTurnContext) : ITurnContext /// The inner context's activity. T ITurnContext.Activity => (T)_innerTurnContext.Activity; - public IConnectorClient Connector => TurnState.Get(); - /// /// Gets the bot adapter that created this context object. /// @@ -41,7 +38,7 @@ internal class TypedTurnContext(ITurnContext innerTurnContext) : ITurnContext /// Gets the collection of values cached with the context object for the lifetime of the turn. /// /// The collection of services registered on this context object. - public TurnContextStateCollection TurnState => _innerTurnContext.TurnState; + public ITurnState TurnState => _innerTurnContext.TurnState; /// /// Gets the activity for this turn of the bot. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IBot.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IBot.cs index 7b4a4d26..46c3ec55 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IBot.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IBot.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IChannelAdapter.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/IChannelAdapter.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs index 198bfc4e..2d191925 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IChannelAdapter.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder { /// /// Represents a bot adapter that can connect a bot to a service endpoint. diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddleware.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddleware.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddleware.cs index 51c46809..493cfddb 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddleware.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder { /// /// Encapsulates an asynchronous method that calls the next @@ -51,7 +51,7 @@ public interface IMiddleware /// /// #pragma warning disable CA1716 // Identifiers should not match keywords (we can't change this without breaking binary compat) - Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)); + Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default); #pragma warning restore CA1716 // Identifiers should not match keywords } } diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddlewareSet.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddlewareSet.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddlewareSet.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddlewareSet.cs index 057e9ed4..ca76c433 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/IMiddlewareSet.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IMiddlewareSet.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using System.Threading; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder { public interface IMiddlewareSet { diff --git a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/ITurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs similarity index 99% rename from src/libraries/Core/Microsoft.Agents.Core/Interfaces/ITurnContext.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs index 26143366..f378f8cd 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Interfaces/ITurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; @@ -8,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Core.Interfaces +namespace Microsoft.Agents.BotBuilder { /// /// A method that can participate in send activity events for the current turn. @@ -112,7 +113,7 @@ public interface ITurnContext /// Gets the collection of values cached with the context object for the lifetime of the turn. /// /// The collection of services registered on this context object. - TurnContextStateCollection TurnState { get; } + ITurnState TurnState { get; } /// /// Gets the activity for this turn of the bot. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj index a8d1413c..4e74184f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Microsoft.Agents.BotBuilder.csproj @@ -32,7 +32,8 @@ - + + diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/MiddlewareSet.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/MiddlewareSet.cs index c195314a..cb7013c9 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/MiddlewareSet.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/MiddlewareSet.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System; using System.Collections; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs index 626d4728..86384660 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; @@ -119,7 +118,7 @@ public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken c public static IUserTokenClient GetTokenClient(ITurnContext turnContext) { ArgumentNullException.ThrowIfNull(turnContext); - var userTokenClient = turnContext.TurnState.Get(); + var userTokenClient = turnContext.TurnState.Temp.GetValue(); if (userTokenClient != null) { return userTokenClient; @@ -220,9 +219,9 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt } // Add the login timeout specified in OAuthPromptSettings to TurnState so it can be referenced if polling is needed - if (!turnContext.TurnState.ContainsKey(OAuthTurnStateConstants.OAuthLoginTimeoutKey) && Timeout.HasValue) + if (!turnContext.TurnState.Temp.HasValue(OAuthTurnStateConstants.OAuthLoginTimeoutKey) && Timeout.HasValue) { - turnContext.TurnState.Add(OAuthTurnStateConstants.OAuthLoginTimeoutKey, TimeSpan.FromMilliseconds(Timeout.Value)); + turnContext.TurnState.Temp.SetValue(OAuthTurnStateConstants.OAuthLoginTimeoutKey, TimeSpan.FromMilliseconds(Timeout.Value)); } // Set input hint diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs new file mode 100644 index 00000000..b972533f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs @@ -0,0 +1,501 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Storage; +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Defines a state management object and automates the reading and writing of associated state + /// properties to a storage layer. + /// + /// + /// Each state management object defines a scope for a storage layer. + /// + /// State properties are created within a state management scope, and the Agents SDK + /// defines these scopes: + /// , , and . + /// + /// You can define additional scopes for your bot. + /// + /// + public abstract class BotState : IPropertyManager, IBotState + { + private readonly IStorage _storage; + private CachedBotState _cachedBotState; + + /// + /// Initializes a new instance of the class. + /// + /// The storage layer this state management object will use to store + /// and retrieve state. + /// The key for the state cache for this . + /// This constructor creates a state management object and associated scope. + /// The object uses to persist state property values. + /// The object uses the to cache state within the context for each turn. + /// + /// or + /// is null. + /// + public BotState(IStorage storage, string stateName) + { + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + Name = stateName ?? throw new ArgumentNullException(nameof(stateName)); + } + + public string Name { get; private set; } + + /// + /// Creates a named state property within the scope of a and returns + /// an accessor for the property. + /// + /// The value type of the property. + /// The name of the property. + /// An accessor for the property. + /// is null. + [Obsolete("Use BotState.GetValue and BotState.SetValue")] + public IStatePropertyAccessor CreateProperty(string name) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + return new BotStatePropertyAccessor(this, name); + } + + public bool HasValue(string name) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + var cachedState = GetCachedState(); + return ObjectPath.HasValue(cachedState.State, name); + } + + /// + /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// value. + /// A representing the asynchronous operation. + public void DeleteValue(string name) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + DeletePropertyValue(name); + } + + /// + /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// value. + /// Defines the default value. + /// Invoked when no value been set for the requested state property. + /// If defaultValueFactory is defined as null in that case, the method returns null and + public T GetValue(string name, Func defaultValueFactory = null) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + T result = default; + + try + { + // if T is a value type, lookup up will throw key not found if not found, but as perf + // optimization it will return null if not found for types which are not value types (string and object). + result = GetPropertyValue(name); + + if (result == null && defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + SetValue(name, result); + } + } + catch (KeyNotFoundException) + { + if (defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + SetValue(name, result); + } + } + + return result; + } + + /// + /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// value. + /// value. + /// A representing the asynchronous operation. + public void SetValue(string name, T value) + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + SetPropertyValue(name, value); + } + + /// + /// True if state has been loaded. + /// + /// + public bool IsLoaded() + { + return _cachedBotState != null; + } + + /// + /// Populates the state cache for this from the storage layer. + /// + /// + /// LoadAsync loads State for the specified turn. + /// + /// The context object for this turn. + /// Optional, true to overwrite any existing state cache; + /// or false to load state from storage only if the cache doesn't already exist. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. + public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(turnContext); + + var storageKey = GetStorageKey(turnContext); + + if (ShouldLoad(storageKey, force)) + { + var items = await _storage.ReadAsync([storageKey], cancellationToken).ConfigureAwait(false); + items.TryGetValue(storageKey, out object val); + + if (val is IDictionary asDictionary) + { + _cachedBotState = new CachedBotState(storageKey, asDictionary); + } + else if (val is JsonObject || val is JsonElement) + { + _cachedBotState = new CachedBotState(storageKey, ProtocolJsonSerializer.ToObject>(val)); + } + else if (val == null) + { + // This is the case where the dictionary did not exist in the store. + _cachedBotState = new CachedBotState(storageKey); + } + else + { + // This should never happen + throw new InvalidOperationException("Data is not in the correct format for BotState."); + } + } + } + + private bool ShouldLoad(string storageKey, bool force) + { + var cachedState = GetCachedState(); + if (cachedState != null && cachedState.Key != storageKey) + { + throw new InvalidOperationException($"BotState '{GetType().Name}' is being used by multiple conversations. Verify \"AddTransient\" DI registration."); + } + + return force || cachedState == null || cachedState.State == null; + } + + /// + /// Writes the state cache for this to the storage layer. + /// + /// The context object for this turn. + /// Optional, true to save the state cache to storage; + /// or false to save state to storage only if a property in the cache has changed. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. + public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(turnContext); + + var cachedState = GetCachedState(); + if (cachedState != null && (force || cachedState.IsChanged())) + { + var key = GetStorageKey(turnContext); + var changes = new Dictionary + { + { key, cachedState.State }, + }; + await _storage.WriteAsync(changes, cancellationToken).ConfigureAwait(false); + cachedState.Hash = CachedBotState.ComputeHash(cachedState.State); + return; + } + } + + /// + /// Clears the state cache for this . + /// + /// A task that represents the work queued to execute. + /// This method clears the state cache in the turn context. Call + /// to persist this + /// change in the storage layer. + /// + public virtual void ClearState() + { + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + + // Explicitly setting the hash will mean IsChanged is always true. And that will force a Save. + GetCachedState().Clear(); + } + + /// + /// Deletes any state in storage and the cache for this . + /// + /// The context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. + public virtual async Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + if (IsLoaded()) + { + ClearState(); + } + + var storageKey = GetStorageKey(turnContext); + await _storage.DeleteAsync(new[] { storageKey }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Gets a copy of the raw cached data for this from the turn context. + /// + /// A JSON representation of the cached state. + internal JsonElement Get() + { + var cachedState = GetCachedState(); + return JsonSerializer.SerializeToElement(cachedState.State, ProtocolJsonSerializer.SerializationOptions); + } + + /// + /// Gets the cached bot state instance that wraps the raw cached data for this + /// from the turn context. + /// + /// The cached bot state instance. + internal CachedBotState GetCachedState() + { + return _cachedBotState; + } + + /// + /// When overridden in a derived class, gets the key to use when reading and writing state to and from storage. + /// + /// The context object for this turn. + /// The storage key. + protected abstract string GetStorageKey(ITurnContext turnContext); + + /// + /// Gets the value of a property from the state cache for this . + /// + /// The value type of the property. + /// The name of the property. + /// A task that represents the work queued to execute. + /// If the task is successful, the result contains the property value, otherwise it will be default(T). +#pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) + protected T GetPropertyValue(string propertyName) +#pragma warning restore CA1801 // Review unused parameters + { + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + + var cachedState = GetCachedState(); + return ObjectPath.GetPathValue(cachedState.State, propertyName, true); + } + + /// + /// Deletes a property from the state cache for this . + /// + /// The name of the property. + /// A task that represents the work queued to execute. + protected void DeletePropertyValue(string propertyName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + + var cachedState = GetCachedState(); + cachedState.State.Remove(propertyName); + } + + /// + /// Sets the value of a property in the state cache for this . + /// + /// The name of the property to set. + /// The value to set on the property. + /// A task that represents the work queued to execute. + protected void SetPropertyValue(string propertyName, object value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + + var cachedState = GetCachedState(); + //cachedState.State[propertyName] = value; + ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); + } + + /// + /// Internal cached bot state. + /// + internal class CachedBotState + { + /// + /// Initializes a new instance of the class. + /// + /// Unique state key. Typically the storage key. + /// Initial state for the . + public CachedBotState(string key, IDictionary state = null) + { + State = state ?? new Dictionary(); + Hash = ComputeHash(State); + Key = key; + } + + /// + /// Gets or sets the state as a dictionary of key value pairs. + /// + /// + /// The state as a dictionary of key value pairs. + /// +#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) + public IDictionary State { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + + internal string Hash { get; set; } + + internal string Key { get; set; } + + internal static string ComputeHash(object obj) + { + return ProtocolJsonSerializer.ToJson(obj); + } + + internal bool IsChanged() + { + return Hash != ComputeHash(State); + } + + internal void Clear() + { + State = new Dictionary(); + Hash = string.Empty; + } + } + + #region Obsolete BotStatePropertyAccessor + /// + /// Implements an for a property container. + /// Note the semantics of this accessor are intended to be lazy, this means the Get, Set and Delete + /// methods will first call LoadAsync. This will be a no-op if the data is already loaded. + /// The implication is you can just use this accessor in the application code directly without first calling LoadAsync + /// this approach works with the AutoSaveStateMiddleware which will save as needed at the end of a turn. + /// + /// type of value the propertyAccessor accesses. + private class BotStatePropertyAccessor : IStatePropertyAccessor + { + private BotState _botState; + + public BotStatePropertyAccessor(BotState botState, string name) + { + _botState = botState; + Name = name; + } + + /// + /// Gets name of the property. + /// + /// + /// name of the property. + /// + public string Name { get; private set; } + + /// + /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// The turn context. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + _botState.DeleteValue(Name); + } + + /// + /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// The context object for this turn. + /// Defines the default value. + /// Invoked when no value been set for the requested state property. + /// If defaultValueFactory is defined as null in that case, the method returns null and + /// SetAsync is not called. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) + { + T result = default(T); + + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + + try + { + // if T is a value type, lookup up will throw key not found if not found, but as perf + // optimization it will return null if not found for types which are not value types (string and object). + result = _botState.GetValue(Name, defaultValueFactory); + + if (result == null && defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); + } + } + catch (KeyNotFoundException) + { + if (defaultValueFactory != null) + { + // use default Value Factory and save default value for any further calls + result = defaultValueFactory(); + await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); + } + } + + return result; + } + + /// + /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. + /// + /// turn context. + /// value. + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken) + { + await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + _botState.SetValue(Name, value); + } + } + #endregion + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs new file mode 100644 index 00000000..59b3a9ed --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Storage; +using System; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Defines a state keyed to a conversation. + /// + /// + /// Conversation state is available in any turn in a specific conversation, regardless of user, + /// such as in a group conversation. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. + /// + /// The storage layer to use. + public class ConversationState(IStorage storage) : BotState(storage, ScopeName) + { + public static readonly string ScopeName = "conversation"; + + /// + /// Gets the key to use when reading and writing state to and from storage. + /// + /// The context object for this turn. + /// The storage key. + /// + /// Conversation state includes the channel ID and conversation ID as part of its storage key. + /// + protected override string GetStorageKey(ITurnContext turnContext) + { + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); + return $"{channelId}/conversations/{conversationId}"; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs new file mode 100644 index 00000000..88ae4aab --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + public interface IBotState + { + string Name { get; } + + void ClearState(); + Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); + void DeleteValue(string name); + T GetValue(string name, Func defaultValueFactory = null); + bool IsLoaded(); + Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + void SetValue(string name, T value); + bool HasValue(string name); + } +} \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IPropertyManager.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IPropertyManager.cs new file mode 100644 index 00000000..28d4ea19 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IPropertyManager.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// IPropertyManager defines implementation of a source of named properties. + /// + public interface IPropertyManager + { + /// + /// Creates a managed state property accessor for a property. + /// + /// The property value type. + /// The name of the property accessor. + /// A state property accessor for the property. + [Obsolete("Use BotState.GetPropertyAsync")] + IStatePropertyAccessor CreateProperty(string name); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyAccessor.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyAccessor.cs new file mode 100644 index 00000000..951bcbb3 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyAccessor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Interface which defines methods for how you can get data from a property source, + /// such as . + /// + /// type of the property. + public interface IStatePropertyAccessor : IStatePropertyInfo + { + /// + /// Gets the property value from the source. + /// + /// Turn Context. + /// Function which defines the property value to be returned if no value has been set. + /// The cancellation token. + /// A representing the result of the asynchronous operation. + Task GetAsync(ITurnContext turnContext, Func defaultValueFactory = null, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Delete the property from the source. + /// + /// Turn Context. + /// The cancellation token. + /// A representing the asynchronous operation. + Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Set the property value on the source. + /// + /// Turn Context. + /// The value to set. + /// The cancellation token. + /// A representing the asynchronous operation. + Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyInfo.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyInfo.cs new file mode 100644 index 00000000..957bc6ac --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IStatePropertyInfo.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Metadata about a property, including policy info. + /// + public interface IStatePropertyInfo + { + /// + /// Gets the name of the property. + /// + /// + /// The name of the property. + /// + string Name { get; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs new file mode 100644 index 00000000..d4daf607 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + public interface ITurnState + { + ConversationState Conversation { get; } + PrivateConversationState Private { get; } + TempState Temp { get; } + UserState User { get; } + + IBotState GetScope(string scope); + T GetScope(); + + T GetValue(string path, Func defaultValueFactory = null); + void SetValue(string path, object value); + void DeleteValue(string path); + bool HasValue(string path); + + void ClearState(string scope); + Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectMerge.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectMerge.cs new file mode 100644 index 00000000..7772db63 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectMerge.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace Microsoft.Agents.BotBuilder.State +{ + internal class ObjectMerge + { + public static JsonNode Merge(JsonNode originalNode, JsonNode newNode) + { + JsonElement originalElement = JsonSerializer.SerializeToElement(originalNode); + JsonElement newElement = JsonSerializer.SerializeToElement(newNode); + + if (originalElement.ValueKind != JsonValueKind.Array && originalElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The original JSON to merge new content into must be a container type. Instead it is {originalElement.ValueKind}."); + } + + var outputBuffer = new ArrayBufferWriter(); + using (var jsonWriter = new Utf8JsonWriter(outputBuffer, new JsonWriterOptions { Indented = true })) + { + if (originalElement.ValueKind != newElement.ValueKind) + { + return originalNode; + } + + if (originalElement.ValueKind == JsonValueKind.Array) + { + MergeArrays(jsonWriter, originalElement, newElement); + } + else + { + MergeObjects(jsonWriter, originalElement, newElement); + } + } + + return JsonSerializer.Deserialize(Encoding.UTF8.GetString(outputBuffer.WrittenSpan)); + } + + private static void MergeObjects(Utf8JsonWriter jsonWriter, JsonElement root1, JsonElement root2) + { + Debug.Assert(root1.ValueKind == JsonValueKind.Object); + Debug.Assert(root2.ValueKind == JsonValueKind.Object); + + jsonWriter.WriteStartObject(); + + // Write all the properties of the first document. + // If a property exists in both documents, either: + // * Merge them, if the value kinds match (e.g. both are objects or arrays), + // * Completely override the value of the first with the one from the second, if the value kind mismatches (e.g. one is object, while the other is an array or string), + // * Or favor the value of the first (regardless of what it may be), if the second one is null (i.e. don't override the first). + foreach (JsonProperty property in root1.EnumerateObject()) + { + string propertyName = property.Name; + + JsonValueKind newValueKind; + + if (root2.TryGetProperty(propertyName, out JsonElement newValue) && (newValueKind = newValue.ValueKind) != JsonValueKind.Null) + { + jsonWriter.WritePropertyName(propertyName); + + JsonElement originalValue = property.Value; + JsonValueKind originalValueKind = originalValue.ValueKind; + + if (newValueKind == JsonValueKind.Object && originalValueKind == JsonValueKind.Object) + { + MergeObjects(jsonWriter, originalValue, newValue); // Recursive call + } + else if (newValueKind == JsonValueKind.Array && originalValueKind == JsonValueKind.Array) + { + MergeArrays(jsonWriter, originalValue, newValue); + } + else + { + newValue.WriteTo(jsonWriter); + } + } + else + { + property.WriteTo(jsonWriter); + } + } + + // Write all the properties of the second document that are unique to it. + foreach (JsonProperty property in root2.EnumerateObject()) + { + if (!root1.TryGetProperty(property.Name, out _)) + { + property.WriteTo(jsonWriter); + } + } + + jsonWriter.WriteEndObject(); + } + + private static void MergeArrays(Utf8JsonWriter jsonWriter, JsonElement root1, JsonElement root2) + { + Debug.Assert(root1.ValueKind == JsonValueKind.Array); + Debug.Assert(root2.ValueKind == JsonValueKind.Array); + + jsonWriter.WriteStartArray(); + + // Write all the elements from both JSON arrays + foreach (JsonElement element in root1.EnumerateArray()) + { + element.WriteTo(jsonWriter); + } + foreach (JsonElement element in root2.EnumerateArray()) + { + element.WriteTo(jsonWriter); + } + + jsonWriter.WriteEndArray(); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectPath.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectPath.cs new file mode 100644 index 00000000..8de34379 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ObjectPath.cs @@ -0,0 +1,820 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Helper methods for working with dynamic json objects. + /// + public static class ObjectPath + { + private static readonly JsonSerializerOptions _serializerOptions = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + IncludeFields = true, + }; + + /// + /// Does an object have a subpath. + /// + /// object. + /// path to evaluate. + /// true if the path is there. + public static bool HasValue(object obj, string path) + { + return TryGetPathValue(obj, path, out var value); + } + + /// + /// Get the value for a path relative to an object. + /// + /// type to return. + /// object to start with. + /// path to evaluate. + /// Set path value with result if true. + /// value or default(T). + public static T GetPathValue(object obj, string path, bool set = false) + { + if (TryGetPathValue(obj, path, out var value, set)) + { + return value; + } + + throw new KeyNotFoundException(path); + } + + /// + /// Get the value for a path relative to an object. + /// + /// type to return. + /// object to start with. + /// path to evaluate. + /// default value to use if any part of the path is missing. + /// Set path value with result if true. + /// value or default(T). + public static T GetPathValue(object obj, string path, T defaultValue, bool set = false) + { + if (TryGetPathValue(obj, path, out var value, set)) + { + return value; + } + + return defaultValue; + } + + /// + /// Get the value for a path relative to an object. + /// + /// type to return. + /// object to start with. + /// path to evaluate. + /// value for the path. + /// If the value was converted, set the path value with the converted value. + /// true if successful. + public static bool TryGetPathValue(object obj, string path, out T value, bool convertedSet = false) + { + value = default; + + if (obj == null) + { + return false; + } + + if (path == null) + { + return false; + } + + if (path.Length == 0) + { + value = MapValueTo(obj); + return true; + } + + if (!TryResolvePath(obj, path, out var segments)) + { + return false; + } + + if (!ResolveSegments(obj, segments, out var result)) + { + return false; + } + + // look to see if it's ExpressionProperty and bind it if it is + // NOTE: this bit of duck typing keeps us from adding dependency between adaptiveExpressions and Dialogs. + if (result.GetType().GetProperty("ExpressionText") != null) + { + var method = result.GetType().GetMethod("GetValue", new[] { typeof(object) }); + if (method != null) + { + result = method.Invoke(result, new[] { obj }); + } + } + + try + { + value = MapValueTo(result); + if (convertedSet && value.GetType() != result.GetType()) + { + SetPathValueInner(segments, obj, value, false); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + return false; + } + + return true; + } + + /// + /// Given an object evaluate a path to set the value. + /// + /// object to start with. + /// path to evaluate. + /// value to store. + /// if true, sets the value as primitive JSON objects. + public static void SetPathValue(object obj, string path, object value, bool json = true) + { + if (!TryResolvePath(obj, path, out var segments)) + { + return; + } + + SetPathValueInner(segments, obj, value, json); + } + + private static void SetPathValueInner(List segments, object obj, object value, bool json = true) + { + dynamic current = obj; + for (var i = 0; i < segments.Count - 1; i++) + { + var segment = segments[i]; + dynamic next; + if (segment is int index) + { + next = current[index]; + if (next == null) + { + if (((ICollection)current).Count <= index) + { + // Expand array to index + for (var idx = ((ICollection)current).Count; idx <= index; ++idx) + { + ((JsonArray)current)[idx] = null; + } + + next = current[index]; + } + } + } + else + { + var strSegment = segment as string; + next = GetObjectProperty(current, strSegment); + if (next == null) + { + // Create object or array base on next segment + var nextSegment = segments[i + 1]; + if (nextSegment is string) + { + SetObjectSegment(current, strSegment, new JsonObject(), json); + next = GetObjectProperty(current, strSegment); + } + else + { + SetObjectSegment(current, strSegment, new JsonArray(), json); + next = GetObjectProperty(current, strSegment); + } + } + } + + current = next; + } + + var lastSegment = segments.Last(); + SetObjectSegment(current, lastSegment, value, json); + } + + /// + /// Remove path from object. + /// + /// Object to change. + /// Path to remove. + public static void RemovePathValue(object obj, string path) + { + if (!TryResolvePath(obj, path, out var segments)) + { + return; + } + + dynamic current = obj; + for (var i = 0; i < segments.Count - 1; i++) + { + var segment = segments[i]; + if (!ResolveSegment(ref current, segment)) + { + return; + } + } + + if (current != null) + { + var lastSegment = segments.Last(); + if (lastSegment is string property) + { + // ConcurrentDictionary doesn't implement Remove, but it does implement IDictionary + if (current is IDictionary dict) + { + dict.Remove(property); + } + else + { + current.Remove(property); + } + } + else + { + current[(int)lastSegment] = null; + } + } + } + + /// + /// Apply an action to all properties in an object. + /// + /// Object to map against. + /// Action to take. + public static void ForEachProperty(object obj, Action action) + { + if (obj is IDictionary dict) + { + foreach (var entry in dict) + { + action(entry.Key, entry.Value); + } + } + else if (obj is JsonObject jobj) + { + foreach (var property in jobj) + { + action(property.Key, property.Value); + } + } + + /* For tracking purposes, only use pure dictionary/jobject. + else if (!(obj.GetType().IsPrimitive || obj.GetType().IsArray() || obj is string || obj is DateTime || obj is DateTimeOffset || obj is JValue || obj is JArray)) + { + foreach (var property in obj.GetType().GetProperties()) + { + // Check for indexer + if (property.GetIndexParameters().Length == 0) + { + action(property.Name, property.GetValue(obj)); + } + } + } + */ + } + + /// + /// Get all properties in an object. + /// + /// Object to enumerate property names. + /// enumeration of property names on the object if it is not a value type. + public static IEnumerable GetProperties(object obj) + { + if (obj == null) + { + } + else if (obj is IDictionary dict) + { + foreach (var entry in dict) + { + yield return entry.Key; + } + } + else if (obj is JsonObject jobj) + { + foreach (var property in jobj) + { + yield return property.Key; + } + } + else + { + foreach (var property in obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name)) + { + yield return property; + } + } + } + + /// + /// Detects if property exists on object. + /// + /// object. + /// name of the property. + /// true if found. + public static bool ContainsProperty(object obj, string name) + { + if (obj == null) + { + return false; + } + + if (obj is IDictionary dict) + { + return dict.ContainsKey(name); + } + + if (obj is JsonObject jobj) + { + return jobj.ContainsKey(name); + } + + return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Any(property => property.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Clone an object. + /// + /// Type to clone. + /// The object. + /// The object as Json. + public static T Clone(T obj) + { + return ProtocolJsonSerializer.CloneTo(obj); + } + + /// + /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. + /// + /// The object type. + /// Intial object. + /// Overlay object. + /// merged object. + public static T Merge(T startObject, T overlayObject) + where T : class + { + return Assign(startObject, overlayObject); + } + + /// + /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. + /// + /// The target type. + /// intial object of any type. + /// overlay object of any type. + /// merged object. + public static T Assign(object startObject, object overlayObject) + where T : class + { + return (T)Assign(startObject, overlayObject, typeof(T)); + } + + /// + /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. + /// + /// intial object of any type. + /// overlay object of any type. + /// type to output. + /// merged object. + public static object Assign(object startObject, object overlayObject, Type type) + { + if (startObject != null && overlayObject != null) + { + // make a deep clone JObject of the startObject + var jsMerged = startObject is JsonObject ? (JsonObject)(startObject as JsonObject).DeepClone() : JsonSerializer.SerializeToNode(startObject, _serializerOptions); + + // get a JObject of the overlay object + var jsOverlay = overlayObject is JsonObject ? overlayObject as JsonObject : JsonSerializer.SerializeToNode(overlayObject, _serializerOptions); + + var merged = ObjectMerge.Merge(jsMerged, jsOverlay); + + /* + jsMerged.Merge(jsOverlay, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Replace, + MergeNullValueHandling = MergeNullValueHandling.Ignore, + }); + */ + + return merged.Deserialize(type, _serializerOptions); + } + + var singleObject = startObject ?? overlayObject; + if (singleObject != null) + { + if (singleObject is JsonObject) + { + return (singleObject as JsonObject).Deserialize(type); + } + + return singleObject; + } + + return (Type)Activator.CreateInstance(type); + } + + /// + /// Convert a generic object to a typed object. + /// + /// type to convert to. + /// value to convert. + /// converted value. + public static T MapValueTo(object val) + { + if (val is JsonElement) + { + T result = ProtocolJsonSerializer.ToObject(val); + return result; + } + + if (typeof(T) == typeof(object)) + { + return (T)val; + } + + if (val is JsonValue valValue) + { + if (valValue.TryGetValue(out T value)) + { + return value; + } + + if (valValue.GetValueKind() == JsonValueKind.Number && typeof(T) == typeof(string)) + { + var number = valValue.GetValue(); + return ProtocolJsonSerializer.ToObject(number.ToString()); + } + + if (valValue.GetValueKind() == JsonValueKind.String && typeof(T) == typeof(int)) + { + var str = valValue.GetValue(); + return ProtocolJsonSerializer.ToObject(str); + } + } + + if (val is JsonNode valNode) + { + return valNode.Deserialize(_serializerOptions); + } + + if (typeof(T) == typeof(JsonNode)) + { + return JsonSerializer.Deserialize(JsonSerializer.Serialize(val, _serializerOptions), _serializerOptions); + } + + if (val is T t) + { + return t; + } + + if (val is short || val is int || val is long || + val is ushort || val is uint || val is ulong || + val is decimal || val is float || val is double) + { + return (T)Convert.ChangeType(val, typeof(T)); + } + + var json = JsonSerializer.Serialize(val, _serializerOptions); + return JsonSerializer.Deserialize(json, _serializerOptions); + } + + /// + /// Given an root object and property path, resolve to a constant if eval = true or a constant path otherwise. + /// conversation[user.name][user.age] => ['conversation', 'joe', 32]. + /// + /// root object. + /// property path to resolve. + /// Path segments. + /// True to evaluate resulting segments. + /// True if it was able to resolve all nested references. + public static bool TryResolvePath(object obj, string propertyPath, out List segments, bool eval = false) + { + var soFar = new List(); + segments = soFar; + var first = propertyPath.Length > 0 ? propertyPath[0] : ' '; + if (first == '\'' || first == '"') + { + if (!propertyPath.EndsWith(first.ToString(), StringComparison.Ordinal)) + { + return false; + } + + soFar.Add(propertyPath.Substring(1, propertyPath.Length - 2)); + } + else if (int.TryParse(propertyPath, out var number)) + { + soFar.Add(number); + } + else + { + var start = 0; + int i; + + // Emit current fragment + void Emit() + { + var segment = propertyPath.Substring(start, i - start); + if (!string.IsNullOrEmpty(segment)) + { + soFar.Add(segment); + } + + start = i + 1; + } + + // Scan path evaluating as we go + for (i = 0; i < propertyPath.Length; ++i) + { + var ch = propertyPath[i]; + if (ch == '.' || ch == '[') + { + Emit(); + } + + if (ch == '[') + { + // Bracket expression + var nesting = 1; + while (++i < propertyPath.Length) + { + ch = propertyPath[i]; + if (ch == '[') + { + ++nesting; + } + else if (ch == ']') + { + --nesting; + if (nesting == 0) + { + break; + } + } + } + + if (nesting > 0) + { + // Unbalanced brackets + return false; + } + + var expr = propertyPath.Substring(start, i - start); + start = i + 1; + if (!TryResolvePath(obj, expr, out var indexer, true) || indexer.Count != 1) + { + // Could not resolve bracket expression + return false; + } + + var result = MapValueTo(indexer.First()); + if (int.TryParse(result, out var index)) + { + soFar.Add(index); + } + else + { + soFar.Add(result); + } + } + } + + Emit(); + + if (eval) + { + if (!ResolveSegments(obj, soFar, out var result)) + { + return false; + } + + soFar.Clear(); + soFar.Add(MapValueTo(result)); + } + } + + return true; + } + + private static bool ResolveSegment(ref dynamic current, object segment) + { + if (current != null) + { + if (segment is int index) + { + current = current[index]; + } + else + { + current = GetObjectProperty(current, segment as string); + } + + // This interprets any null value as not being present. + return (object)current != null; + } + + return false; + } + + private static bool ResolveSegments(dynamic current, List segments, out object result) + { + result = current; + foreach (var segment in segments) + { + if (!ResolveSegment(ref result, segment)) + { + return false; + } + } + + return true; + } + + /// + /// Get a property or array element from an object. + /// + /// object. + /// property or array segment to get relative to the object. + /// the value or null if not found. + private static object GetObjectProperty(object obj, string property) + { + if (obj == null) + { + return null; + } + + if (obj is IDictionary dict) + { + var key = dict.Keys.Where(key => string.Equals(key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? property; + if (dict.TryGetValue(key, out var value)) + { + return value; + } + + return null; + } + + if (obj is JsonObject jobj) + { + var key = jobj.AsEnumerable().Where(kvPair => string.Equals(kvPair.Key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Key ?? property; + jobj.TryGetPropertyValue(key, out var value); + return value; + } + + if (obj is JsonValue jval) + { + // in order to make things like "this.value.Length" work, when "this.value" is a string. + //return GetObjectProperty(JsonSerializer.SerializeToNode(jval), property); + throw new NotImplementedException(); + } + + var prop = obj.GetType().GetProperties().Where(p => string.Equals(p.Name, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + if (prop != null) + { + return prop.GetValue(obj); + } + + return null; + } + + /// + /// Given an object, set a property or array element on it with a value. + /// + /// object to modify. + /// property or array segment to put the value in. + /// value to store. + /// if true, value will be normalized to JSON primitive objects. + private static void SetObjectSegment(object obj, object segment, object value, bool json = true) + { + object val; + + val = GetNormalizedValue(value, json); + if (segment is int index) + { + if (obj is JsonArray jarray) + { + // grow array if required + var jar = obj as JsonArray; + for (var i = jar.Count; i <= index; i++) + { + jar.Add(null); + } + + jar[index] = JsonSerializer.SerializeToNode(val); + } + else if (obj is Array array) + { + if (index >= array.Length) + { + // TODO + throw new ArgumentException("Cannot grow arrays yet"); + } + + array.SetValue(value, index); + } + else if (obj is IList list) + { + if (index >= list.Count) + { + for (var i = list.Count; i <= index; i++) + { + list.Add(null); + } + } + + list[index] = value; + } + return; + } + + var property = segment as string; + if (obj is IDictionary dict) + { + // For case insensitive key + var key = dict.Keys.Where(k => string.Equals(k, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? property; + dict[key] = value; + return; + } + + if (obj is JsonObject jobj) + { + // For case insensitive key + var key = jobj.Where(p => string.Equals(p.Key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Key ?? property; + jobj[key] = val is JsonNode valNode ? valNode : JsonSerializer.SerializeToNode(val); + return; + } + + var prop = obj.GetType().GetProperty(property); + if (prop != null) + { + if (prop.GetValue(obj) != value) + { + prop.SetValue(obj, value); + } + } + } + + /// + /// Normalize value as json objects. + /// + /// value to normalize. + /// normalize as json objects. + /// normalized value. + private static object GetNormalizedValue(object value, bool json) + { + object val; + if (json) + { + if (value is JsonNode node) + { + val = node.DeepClone(); + } + else if (value is JsonElement) + { + val = JsonSerializer.SerializeToNode(value, _serializerOptions); + } + else if (value == null) + { + val = null; + } + else if (value is string || value is byte || value is bool || + value is DateTime || value is DateTimeOffset || + value is short || value is int || value is long || + value is ushort || value is uint || value is ulong || + value is decimal || value is float || value is double) + { + val = JsonValue.Create(value); + } + else + { + var jsonValue = JsonSerializer.Serialize(value, _serializerOptions); + val = JsonObject.Parse(jsonValue, new JsonNodeOptions() { PropertyNameCaseInsensitive = true }); + } + } + else + { + val = value; + } + + return val; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs new file mode 100644 index 00000000..2dc9fa9e --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Storage; +using System; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Defines a state keyed to a conversation and user. + /// + /// + /// Conversation state is available in any turn in a specific conversation, regardless of user, + /// such as in a group conversation. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. + /// + /// The storage layer to use. + public class PrivateConversationState(IStorage storage) : BotState(storage, ScopeName) + { + public static readonly string ScopeName = "private"; + + /// + /// Gets the key to use when reading and writing state to and from storage. + /// + /// The context object for this turn. + /// The storage key. + /// + /// Private conversation state includes the channel ID, conversation ID, and user ID as part + /// of its storage key. + /// + protected override string GetStorageKey(ITurnContext turnContext) + { + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); + var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); + return $"{channelId}/conversations/{conversationId}/users/{userId}"; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs new file mode 100644 index 00000000..b837e21d --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + public class TempState : IBotState + { + public static readonly string ScopeName = "temp"; + private readonly Dictionary _state = []; + + public string Name => ScopeName; + + public void ClearState() + { + _state.Clear(); + } + + public Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + _state.Clear(); + return Task.CompletedTask; + } + + public bool HasValue(string name) + { + return _state.ContainsKey(name); + } + + public void DeleteValue(string name) + { + _state.Remove(name); + } + + public T GetValue(string name, Func defaultValueFactory = null) + { + if (!_state.TryGetValue(name, out var value)) + { + if (defaultValueFactory != null) + { + value = defaultValueFactory(); + SetValue(name, value); + } + } + + return (T) value; + } + + public void SetValue(string name, T value) + { + _state[name] = value; + } + + public T GetValue() + { + return GetValue(typeof(T).FullName); + } + + public void SetValue(T value) + { + SetValue(typeof(T).FullName, value); + } + + public bool IsLoaded() + { + return true; + } + + public Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TurnState.cs new file mode 100644 index 00000000..2fc893cd --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TurnState.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Agents.Storage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Manages a collection of botState and provides ability to load and save in parallel. + /// + public class TurnState : ITurnState + { + private readonly Dictionary _scopes = []; + + /// + /// Initializes a new instance of the class. + /// + /// initial list of objects to manage. + public TurnState(params IBotState[] botStates) + { + foreach (var botState in botStates) + { + _scopes.Add(botState.Name, botState); + } + + if (!TryGetScope(out _)) + { + _scopes.Add(TempState.ScopeName, new TempState()); + } + } + + /// + /// Creates BotStateSet with default ConversationState and UserState + /// + /// + /// Additional list of BotState objects to manage. + public TurnState(IStorage storage, params IBotState[] botStates) + { + _scopes.Add(ConversationState.ScopeName, new ConversationState(storage)); + _scopes.Add(UserState.ScopeName, new UserState(storage)); + _scopes.Add(TempState.ScopeName, new TempState()); + + foreach (var botState in botStates) + { + _scopes[botState.Name] = botState; + } + } + + public ConversationState Conversation => GetScope(); + public UserState User => GetScope(); + public PrivateConversationState Private => GetScope(); + public TempState Temp => GetScope(); + + public bool HasValue(string path) + { + var (scope, property) = GetScopeAndPath(path); + return GetScope(scope).HasValue(property); + } + + public T GetValue(string name, Func defaultValueFactory = null) + { + var (scope, property) = GetScopeAndPath(name); + return GetScope(scope).GetValue(property, defaultValueFactory); + } + + public void SetValue(string path, object value) + { + var (scope, property) = GetScopeAndPath(path); + GetScope(scope).SetValue(property, value); + } + + public void DeleteValue(string path) + { + var (scope, property) = GetScopeAndPath(path); + GetScope(scope).DeleteValue(property); + } + + public IBotState GetScope(string scope) + { + if (!_scopes.TryGetValue(scope, out IBotState value)) + { + throw new ArgumentException($"Scope '{scope}' not found"); + } + return value; + } + + public T GetScope() + { + if (TryGetScope(out var scope)) + { + return scope; + } + throw new ArgumentException($"Scope '{nameof(T)}' not found"); + } + + public bool TryGetScope(out T value) + { + foreach (var scope in _scopes) + { + if (scope.Value is T botState) + { + value = botState; + return true; + } + } + + value = default; + return false; + } + + public TurnState Add(IBotState botState) + { + ArgumentNullException.ThrowIfNull(botState); + _scopes.Add(botState.Name, botState); + return this; + } + + /// + /// Load all BotState records in parallel. + /// + /// turn context. + /// should data be forced into cache. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + var tasks = _scopes.Select(bs => bs.Value.LoadAsync(turnContext, force, cancellationToken)).ToList(); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + public void ClearState(string scope) + { + GetScope(scope).ClearState(); + } + + /// + /// Save All BotState changes in parallel. + /// + /// turn context. + /// should data be forced to save even if no change were detected. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) + { + var tasks = _scopes.Select(kv => kv.Value.SaveChangesAsync(turnContext, force, cancellationToken)).ToList(); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + private static (string, string) GetScopeAndPath(string name) + { + var scopeEnd = name.IndexOf('.'); + if (scopeEnd == -1) + { + return (TempState.ScopeName, name); + } + return (name[..scopeEnd], name[(scopeEnd + 1)..]); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs new file mode 100644 index 00000000..7daa6cc0 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Storage; +using System; + +namespace Microsoft.Agents.BotBuilder.State +{ + /// + /// Defines a state keyed to a user. + /// + /// + /// Conversation state is available in any turn in a specific conversation, regardless of user, + /// such as in a group conversation. + /// + /// This implementation should NOT be used as a singleton. This includes registering as singleton + /// in DI. + /// + /// The storage layer to use. + public class UserState(IStorage storage) : BotState(storage, ScopeName) + { + public static readonly string ScopeName = "user"; + + /// + /// Gets the key to use when reading and writing state to and from storage. + /// + /// The context object for this turn. + /// The storage key. + /// + /// User state includes the channel ID and user ID as part of its storage key. + /// + protected override string GetStorageKey(ITurnContext turnContext) + { + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); + return $"{channelId}/users/{userId}"; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs index 065994ce..03d4892d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; @@ -35,13 +35,15 @@ public class TurnContext : ITurnContext, IDisposable /// The adapter creating the context. /// The incoming activity for the turn; /// or null for a turn for a proactive message. + /// /// or /// is null. /// For use by bot adapter implementations only. - public TurnContext(IChannelAdapter adapter, IActivity activity) + public TurnContext(IChannelAdapter adapter, IActivity activity, ITurnState state = null) { Adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); Activity = activity ?? throw new ArgumentNullException(nameof(activity)); + TurnState = state ?? (ITurnState) new TurnState(); } /// @@ -85,7 +87,7 @@ public TurnContext(ITurnContext turnContext, IActivity activity) /// Gets the services registered on this context object. /// /// The services registered on this context object. - public TurnContextStateCollection TurnState { get; } = []; + public ITurnState TurnState { get; } /// /// Gets the activity associated with this turn; or null when processing @@ -272,7 +274,7 @@ async Task SendActivitiesThroughAdapter() // is not being sent through the adapter, where it would be added to TurnState. if (activity.Type == ActivityTypes.InvokeResponse) { - TurnState.Add(ChannelAdapter.InvokeResponseKey, activity); + TurnState.Temp.SetValue(ChannelAdapter.InvokeResponseKey, activity); } responses[index] = new ResourceResponse(); @@ -388,11 +390,6 @@ protected virtual void Dispose(bool disposing) return; } - if (disposing) - { - TurnState.Dispose(); - } - _disposed = true; } diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/HandoffEventFactory.cs b/src/libraries/Core/Microsoft.Agents.Core/Models/HandoffEventFactory.cs index cf41decb..8337171e 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/HandoffEventFactory.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Models/HandoffEventFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System; using System.Collections.Generic; @@ -15,21 +14,21 @@ public static class HandoffEventFactory /// /// Create handoff initiation event. /// - /// turn context. + /// Incoming activity /// agent hub-specific context. /// transcript of the conversation. /// handoff event. - public static Activity CreateHandoffInitiation(ITurnContext turnContext, object handoffContext, Transcript transcript = null) + public static IActivity CreateHandoffInitiation(IActivity activity, object handoffContext, Transcript transcript = null) { - ArgumentNullException.ThrowIfNull(turnContext); + ArgumentNullException.ThrowIfNull(activity); - var handoffEvent = CreateHandoffEvent(HandoffEventNames.InitiateHandoff, handoffContext, turnContext.Activity.Conversation); + var handoffEvent = CreateHandoffEvent(HandoffEventNames.InitiateHandoff, handoffContext, activity.Conversation); - handoffEvent.From = turnContext.Activity.From; - handoffEvent.RelatesTo = turnContext.Activity.GetConversationReference(); - handoffEvent.ReplyToId = turnContext.Activity.Id; - handoffEvent.ServiceUrl = turnContext.Activity.ServiceUrl; - handoffEvent.ChannelId = turnContext.Activity.ChannelId; + handoffEvent.From = activity.From; + handoffEvent.RelatesTo = activity.GetConversationReference(); + handoffEvent.ReplyToId = activity.Id; + handoffEvent.ServiceUrl = activity.ServiceUrl; + handoffEvent.ChannelId = activity.ChannelId; if (transcript != null) { diff --git a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs index e2b0b545..9ad885aa 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs index 0a2c0c35..37ed2d2b 100644 --- a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs +++ b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs @@ -13,6 +13,7 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector.Types; using System.Text; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.Hosting.AspNetCore { @@ -41,9 +42,10 @@ public class CloudAdapter public CloudAdapter( IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, + ITurnState state = null, ILogger logger = null, bool async = true, - Core.Interfaces.IMiddleware[] middlewares = null) : base(channelServiceClientFactory, logger) + BotBuilder.IMiddleware[] middlewares = null) : base(channelServiceClientFactory, state, logger: logger) { _activityTaskQueue = activityTaskQueue ?? throw new ArgumentNullException(nameof(activityTaskQueue)); _async = async; diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 723a9de2..7fda502d 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Client; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; using Microsoft.Agents.Storage; using Microsoft.Extensions.Configuration; diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs index 3d53a940..034dc99a 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.SharePoint.Models; diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs index 967ded77..83770d91 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs @@ -11,9 +11,9 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector; using Microsoft.Agents.SharePoint.Compat; +using Microsoft.Agents.BotBuilder; namespace Microsoft.Agents.SharePoint { @@ -143,7 +143,7 @@ private async Task ExchangedTokenAsync(ITurnContext turnContext, Cancellat try { - var userTokenClient = turnContext.TurnState.Get(); + var userTokenClient = turnContext.TurnState.Temp.GetValue(); if (userTokenClient != null) { tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync( diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs index 90c205cd..99e47fef 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs index dcc6a2e0..3821a720 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs index 2efeb5e4..9d4d157f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs index ca4a555d..847e3df2 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs @@ -1,6 +1,6 @@  +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs index deb4f668..a6a681c0 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs index ca199f54..46683cd6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs @@ -2,7 +2,6 @@ using Microsoft.Agents.BotBuilder.Application; using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -110,7 +109,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.Data, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -207,7 +206,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.BotActivityPreview[0], cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -308,7 +307,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec await handler(turnContext, turnState, activityPreview, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new(); Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); @@ -403,7 +402,7 @@ public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTa TaskModuleResponse result = await handler(turnContext, turnState, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -504,7 +503,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle MessagingExtensionResult result = await handler(turnContext, turnState, query, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -577,7 +576,7 @@ public Application OnSelectItem(SelectItemHandlerAsync handler) MessagingExtensionResult result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -610,7 +609,7 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -648,7 +647,7 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync ha MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -683,7 +682,7 @@ public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync MessagingExtensionResult result = await handler(turnContext, turnState, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -718,7 +717,7 @@ public Application OnConfigureSettings(ConfigureSettingsHandler await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -751,7 +750,7 @@ public Application OnCardButtonClicked(CardButtonClickedHandler await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs index 78bcdbf1..c6d5b1b4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs @@ -1,6 +1,6 @@ -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Models; using System.Collections.Generic; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs index 9a7bdf27..fc0bf9c9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs index f71ab9b2..00818d32 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs index e4befee6..d7cf454d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs @@ -1,7 +1,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Application; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -99,7 +98,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -210,7 +209,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs index e4a609c1..9436e6de 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Application.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs index 0d34b58c..4dde5014 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs @@ -1,5 +1,4 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -238,7 +237,7 @@ public Application OnConfigFetch(ConfigHandlerAsync handler) ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -265,7 +264,7 @@ public Application OnConfigSubmit(ConfigHandlerAsync handler) ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -311,7 +310,7 @@ private Application OnFileConsent(FileConsentHandler handler, st await handler(turnContext, turnState, fileConsentCardResponse, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -340,7 +339,7 @@ public Application OnO365ConnectorCardAction(O365ConnectorCardActionHand await handler(turnContext, turnState, query, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -380,7 +379,7 @@ public Application OnFeedbackLoop(FeedbackLoopHandler handler) await handler(turnContext, turnState, feedbackLoopData, cancellationToken); // Check to see if an invoke response has already been added - if (turnContext.TurnState.Get(ChannelAdapter.InvokeResponseKey) == null) + if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs index 70b61407..9cab53f6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Connector.Types; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Connector; diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs index 0a7f4a42..eb94325c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs @@ -9,8 +9,8 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector; +using Microsoft.Agents.BotBuilder; namespace Microsoft.Agents.Teams.Compat { @@ -127,7 +127,7 @@ private async Task ExchangedTokenAsync(ITurnContext turnContext, Cancellat try { - var userTokenClient = turnContext.TurnState.Get(); + var userTokenClient = turnContext.TurnState.Temp.GetValue(); if (userTokenClient != null) { tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync( diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs index 0cdc3ab1..0b293ce5 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -450,7 +450,7 @@ private static async Task> GetMembersAsync(ICon private static IConnectorClient GetConnectorClient(ITurnContext turnContext) { - return turnContext.TurnState.Get() ?? throw new InvalidOperationException("This method requires a connector client."); + return turnContext.TurnState.Temp.GetValue() ?? throw new InvalidOperationException("This method requires a connector client."); } private static async Task GetMemberAsync(IConnectorClient connectorClient, string userId, string conversationId, CancellationToken cancellationToken) diff --git a/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/FileTranscriptLogger.cs b/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/FileTranscriptLogger.cs index cf6616e6..c81468a9 100644 --- a/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/FileTranscriptLogger.cs +++ b/src/libraries/Storage/Microsoft.Agents.Storage.Transcript/FileTranscriptLogger.cs @@ -8,7 +8,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Text.Json; using System.Threading.Tasks; namespace Microsoft.Agents.Storage.Transcript diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index 949a6c75..269c47b8 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -1,7 +1,7 @@ using EchoBot.Model; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Application; using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Threading; using System.Threading.Tasks; diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs index 24ac1b3e..3214426a 100644 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -7,9 +7,8 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.State; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/samples/AuthenticationBot/AuthenticationBot.csproj b/src/samples/AuthenticationBot/AuthenticationBot.csproj index edf4a6cd..9886ce97 100644 --- a/src/samples/AuthenticationBot/AuthenticationBot.csproj +++ b/src/samples/AuthenticationBot/AuthenticationBot.csproj @@ -7,7 +7,6 @@ - diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index dfbcfedd..02cc93f9 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -9,9 +9,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore; using AuthenticationBot; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.State; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs b/src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs index 3176b617..1584ec9d 100644 --- a/src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs +++ b/src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs @@ -9,7 +9,6 @@ using Microsoft.Agents.Client; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; namespace Microsoft.Agents.Samples.Bots @@ -20,7 +19,7 @@ public class BotHostAdapterWithErrorHandler : CloudAdapter private readonly IChannelHost _botsConfig; public BotHostAdapterWithErrorHandler(IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, IChannelHost botsConfig, ILogger logger) - : base(channelServiceClientFactory, activityTaskQueue, logger) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger) { _botsConfig = botsConfig ?? throw new ArgumentNullException(nameof(botsConfig)); _logger = logger ?? NullLogger.Instance; diff --git a/src/samples/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/BotToBot/Bot1/Bots/Bot1.cs index b40cc492..466c5beb 100644 --- a/src/samples/BotToBot/Bot1/Bots/Bot1.cs +++ b/src/samples/BotToBot/Bot1/Bots/Bot1.cs @@ -13,7 +13,6 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Configuration; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector.Types; @@ -126,7 +125,7 @@ private async Task SendToBot(ITurnContext turnContext, IChannelInfo targetChanne // Create a conversationId to interact with the skill and send the activity var options = new ConversationIdFactoryOptions { - FromBotOAuthScope = turnContext.TurnState.Get(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = turnContext.TurnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey), FromBotId = _channelHost.HostAppId, Activity = turnContext.Activity, Bot = targetChannel diff --git a/src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs b/src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs index b7422b5d..12a28dcf 100644 --- a/src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs +++ b/src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs @@ -7,7 +7,6 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.BotBuilder; namespace Microsoft.Agents.Samples.Bots @@ -17,7 +16,7 @@ public class BotAdapterWithErrorHandler : CloudAdapter private readonly ILogger _logger; public BotAdapterWithErrorHandler(IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, ILogger logger) - : base(channelServiceClientFactory, activityTaskQueue, logger) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); OnTurnError = HandleTurnError; diff --git a/src/samples/BotToBot/Bot2/Bots/Bot2.cs b/src/samples/BotToBot/Bot2/Bots/Bot2.cs index c2f3f654..baec064d 100644 --- a/src/samples/BotToBot/Bot2/Bots/Bot2.cs +++ b/src/samples/BotToBot/Bot2/Bots/Bot2.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; diff --git a/src/samples/CopilotStudioEchoSkill/BotAdapterWithErrorHandler.cs b/src/samples/CopilotStudioEchoSkill/BotAdapterWithErrorHandler.cs index 2af14dba..65aa8c37 100644 --- a/src/samples/CopilotStudioEchoSkill/BotAdapterWithErrorHandler.cs +++ b/src/samples/CopilotStudioEchoSkill/BotAdapterWithErrorHandler.cs @@ -7,7 +7,6 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.BotBuilder; namespace CopilotStudioEchoSkill @@ -20,7 +19,7 @@ public class BotAdapterWithErrorHandler : CloudAdapter private readonly ILogger _logger; public BotAdapterWithErrorHandler(IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, ILogger logger) - : base(channelServiceClientFactory, activityTaskQueue, logger) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs b/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs index 40ee3a11..3b652c2b 100644 --- a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs +++ b/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; diff --git a/src/samples/EchoBot/MyBot.cs b/src/samples/EchoBot/MyBot.cs index 1618ab7e..544247b8 100644 --- a/src/samples/EchoBot/MyBot.cs +++ b/src/samples/EchoBot/MyBot.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; diff --git a/src/samples/SemanticKernel/WeatherBot/MyBot.cs b/src/samples/SemanticKernel/WeatherBot/MyBot.cs index f4b52f43..58fa06c6 100644 --- a/src/samples/SemanticKernel/WeatherBot/MyBot.cs +++ b/src/samples/SemanticKernel/WeatherBot/MyBot.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; diff --git a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs index bd4811bf..2d92c868 100644 --- a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs +++ b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using AdaptiveCards.Templating; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder; namespace AdaptiveCardActions.Bots { diff --git a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs index c3d7b902..68b6e577 100644 --- a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs +++ b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs @@ -14,10 +14,10 @@ using Microsoft.Extensions.Configuration; using AdaptiveCards.Templating; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.BotBuilder; namespace ConversationBot.Bots { diff --git a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs index a4d3d7ec..dc7505fb 100644 --- a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs +++ b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using AdaptiveCards; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Models; diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs index 918f4979..b68a6ee4 100644 --- a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs +++ b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs @@ -12,13 +12,13 @@ using System.Threading; using System.Threading.Tasks; using System.Web; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams; using Microsoft.Agents.Teams.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.BotBuilder; namespace InMeetingNotificationsBot.Bots diff --git a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs index 22d9f43a..06086418 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs @@ -13,8 +13,8 @@ using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder; namespace MessagingExtensionsSearch.Bots diff --git a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs index 9d8698f8..e25429a2 100644 --- a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs +++ b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs @@ -11,9 +11,9 @@ using Microsoft.Agents.Teams.Models; using Microsoft.Extensions.Configuration; using System.Text.Json.Nodes; -using Microsoft.Agents.Core.Interfaces; using TaskModule.Models; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder; namespace TaskModule.Bots { diff --git a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs index acf5f6d1..e57a6333 100644 --- a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs @@ -6,9 +6,9 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; namespace BotAllCards.Bots { diff --git a/src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs b/src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs index c978aeff..d30c4b40 100644 --- a/src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs +++ b/src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs @@ -10,9 +10,9 @@ using AdaptiveCards.Templating; using AdaptiveCards; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using BotAllCards.Dialogs; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; namespace BotAllCards.Bots { diff --git a/src/samples/Teams/bot-all-cards/Program.cs b/src/samples/Teams/bot-all-cards/Program.cs index 36c5a231..774397de 100644 --- a/src/samples/Teams/bot-all-cards/Program.cs +++ b/src/samples/Teams/bot-all-cards/Program.cs @@ -3,7 +3,6 @@ using BotAllCards.Bots; using BotAllCards.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -11,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs index 42241464..d8c1e8bd 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs @@ -5,11 +5,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; namespace BotConversationSsoQuickstart.Bots { diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs index 9eacbfc0..b16e2fb9 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs index 9eb37a03..17adb98c 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs @@ -81,7 +81,7 @@ private async Task InterruptAsync( if (text != null && text.IndexOf("logout") >= 0) { // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Get(); + var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index 80151a0e..660e94e4 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -4,7 +4,6 @@ using BotConversationSsoQuickstart; using BotConversationSsoQuickstart.Bots; using BotConversationSsoQuickstart.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -12,9 +11,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs b/src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs index 6659f8bd..a50d8ba5 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs @@ -3,12 +3,11 @@ // using System; -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; namespace BotConversationSsoQuickstart { @@ -20,7 +19,7 @@ public TeamsSSOAdapter( ILogger logger, ConversationState conversationState, IMiddleware[] middlewares = null) - : base(channelServiceClientFactory, activityTaskQueue, logger, middlewares: middlewares) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger, middlewares: middlewares) { OnTurnError = async (turnContext, exception) => { diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs index eae71672..31d81198 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs @@ -7,8 +7,8 @@ using System.Threading; using System.Threading.Tasks; using AdaptiveCards.Templating; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs index f32fc200..1d0463d0 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -11,6 +10,7 @@ using Microsoft.Extensions.Logging; using PeoplePicker.Bots; using Microsoft.Agents.Teams; +using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs index 96f8354e..1db98009 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder; namespace ReceiveMessagesWithRSC.Bots { diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs index 5f49f965..bb24aaec 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -11,6 +10,7 @@ using Microsoft.Extensions.Logging; using ReceiveMessagesWithRSC.Bots; using Microsoft.Agents.Teams; +using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs index bd4cf4f2..b056d074 100644 --- a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs @@ -4,11 +4,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; namespace TagMentionBot.Bots { diff --git a/src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs b/src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs index c3a64e39..f942a853 100644 --- a/src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs +++ b/src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs index 456b3125..9f022920 100644 --- a/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs @@ -82,7 +82,7 @@ private async Task InterruptAsync( if (text.IndexOf("logout") >= 0) { // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Get(); + var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); diff --git a/src/samples/Teams/bot-tag-mention/Program.cs b/src/samples/Teams/bot-tag-mention/Program.cs index 3894442d..adaf29f2 100644 --- a/src/samples/Teams/bot-tag-mention/Program.cs +++ b/src/samples/Teams/bot-tag-mention/Program.cs @@ -3,7 +3,6 @@ using TagMentionBot.Bots; using TagMentionBot.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -11,9 +10,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs index 7057d6fe..e72f28f9 100644 --- a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs @@ -6,9 +6,9 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; namespace TeamsAuth.Bots { diff --git a/src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs b/src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs index 7d6302a4..d5f8dc75 100644 --- a/src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs +++ b/src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs @@ -7,8 +7,8 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; namespace TeamsAuth.Bots { diff --git a/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs index 3362dcb7..8ef26e70 100644 --- a/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs @@ -53,7 +53,7 @@ public class LogoutDialog(string id, string connectionName) : ComponentDialog(id if (text.IndexOf("logout") >= 0) { // Accesses the UserTokenClient to handle the sign-out process. - var userTokenClient = innerDc.Context.TurnState.Get(); + var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); // Sends a confirmation message and cancels any active dialogs. diff --git a/src/samples/Teams/bot-teams-authentication/Program.cs b/src/samples/Teams/bot-teams-authentication/Program.cs index 815e4297..a680b112 100644 --- a/src/samples/Teams/bot-teams-authentication/Program.cs +++ b/src/samples/Teams/bot-teams-authentication/Program.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.Agents.Samples; @@ -11,6 +10,7 @@ using TeamsAuth.Bots; using TeamsAuth.Dialogs; using Microsoft.Agents.Teams; +using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs index e3e6b423..7a8e58fa 100644 --- a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs +++ b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; @@ -693,7 +692,7 @@ public virtual TurnContext CreateTurnContext(IActivity activity) { var turnContext = new TurnContext(this, activity); - turnContext.TurnState.Add(this); + turnContext.TurnState.Temp.SetValue(this); return turnContext; } diff --git a/src/tests/BotBuilder.Testing/Adapters/TestFlow.cs b/src/tests/BotBuilder.Testing/Adapters/TestFlow.cs index a7cf7b45..488c7260 100644 --- a/src/tests/BotBuilder.Testing/Adapters/TestFlow.cs +++ b/src/tests/BotBuilder.Testing/Adapters/TestFlow.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; diff --git a/src/tests/BotBuilder.Testing/DialogTestClient.cs b/src/tests/BotBuilder.Testing/DialogTestClient.cs index cd2fcf2e..8e0394e0 100644 --- a/src/tests/BotBuilder.Testing/DialogTestClient.cs +++ b/src/tests/BotBuilder.Testing/DialogTestClient.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; using Microsoft.Agents.Storage; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Testing { diff --git a/src/tests/BotBuilder.Testing/TestAdapterTests.cs b/src/tests/BotBuilder.Testing/TestAdapterTests.cs index 203bf66c..91a18ccc 100644 --- a/src/tests/BotBuilder.Testing/TestAdapterTests.cs +++ b/src/tests/BotBuilder.Testing/TestAdapterTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Security; diff --git a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs index f33c3a3a..09546d29 100644 --- a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs +++ b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; @@ -57,7 +56,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) { var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); - context.TurnState[_stopWatchStateKey] = stopwatch; + context.TurnState.Temp.SetValue(_stopWatchStateKey, stopwatch); await LogIncomingActivityAsync(context, context.Activity, cancellationToken).ConfigureAwait(false); context.OnSendActivities(OnSendActivitiesAsync); @@ -99,7 +98,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) /// A task that represents the work to execute. protected virtual Task LogOutgoingActivityAsync(ITurnContext context, IActivity activity, CancellationToken cancellationToken = default(CancellationToken)) { - var stopwatch = (System.Diagnostics.Stopwatch)context.TurnState[_stopWatchStateKey]; + var stopwatch = context.TurnState.Temp.GetValue(_stopWatchStateKey); var actor = "Bot: "; if (activity.Type == ActivityTypes.Message) { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs index 140d0d92..aac36f42 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ActivityPromptTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; @@ -9,7 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Xunit; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs index ed207126..b5aa97e0 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/AttachmentPromptTests.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; using Microsoft.Agents.Core.Models; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs index a9444942..a641e69e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicePromptTests.cs @@ -12,8 +12,8 @@ using Xunit; using Microsoft.Agents.Storage; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.State; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicesChannelTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicesChannelTests.cs index c6b25056..b248fac6 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicesChannelTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ChoicesChannelTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Moq; using Xunit; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs index bbafe5a4..0f3804d9 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/CloudOAuthPromptTests.cs @@ -7,15 +7,14 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Moq; using Xunit; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core; using Microsoft.Recognizers.Text; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs index eb84298e..77badee9 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ComponentDialogTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; @@ -13,7 +12,8 @@ using Microsoft.Agents.Telemetry; using Microsoft.Recognizers.Text; using Xunit; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs index 582c0da4..8764df13 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptLocTests.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Choices; using Microsoft.Agents.BotBuilder.Dialogs.Prompts; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; @@ -13,6 +12,8 @@ using Xunit; using static Microsoft.Agents.BotBuilder.Dialogs.Prompts.PromptCultureModels; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs index 40dad053..b3c9d25b 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ConfirmPromptTests.cs @@ -4,7 +4,6 @@ using System; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; @@ -12,6 +11,8 @@ using Microsoft.Recognizers.Text; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs index b76a87ff..d6d64707 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DateTimePromptTests.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; @@ -14,6 +13,8 @@ using Microsoft.Recognizers.Text; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index b85a6c7c..852463b3 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -15,11 +15,10 @@ using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.State; using Microsoft.Agents.Telemetry; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core; using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { @@ -144,7 +143,7 @@ public async Task RunAsyncShouldSetTelemetryClient() using (var turnContext = new TurnContext(adapter.Object, activity)) { await conversationState.LoadAsync(turnContext, false); - turnContext.TurnState.Add(telemetryClientMock.Object); + turnContext.TurnState.Temp.SetValue(telemetryClientMock.Object); await DialogExtensions.RunAsync(dialog, turnContext, conversationState, CancellationToken.None); } @@ -164,8 +163,8 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc var adapter = new TestAdapter(TestAdapter.CreateConversation(conversationId)); adapter - .UseStorage(storage) - .UseBotState(userState, convoState) + //.UseStorage(storage) + //.UseBotState(userState, convoState) .Use(new AutoSaveStateMiddleware(userState, convoState)) .Use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger(traceActivity: false))); @@ -185,20 +184,20 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); - turnContext.TurnState.Add(ChannelAdapter.BotIdentityKey, claimsIdentity); + turnContext.TurnState.Temp.SetValue(ChannelAdapter.BotIdentityKey, claimsIdentity); if (testCase == FlowTestCase.RootBotConsumingSkill) { // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. // This emulates a response coming to a root bot through SkillHandler. - turnContext.TurnState.Add(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); + turnContext.TurnState.Temp.SetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); } if (testCase == FlowTestCase.MiddleSkill) { // Simulate the SkillConversationReference with a parent Bot ID stored in TurnState. // This emulates a response coming to a skill from another skill through SkillHandler. - turnContext.TurnState.Add(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); + turnContext.TurnState.Temp.SetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogSetTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogSetTests.cs index 366fd930..c4c563b7 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogSetTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogSetTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Storage; using Microsoft.Agents.Telemetry; using System; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs index a02ad7f0..a91c8a5c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/NumberPromptTests.cs @@ -3,13 +3,14 @@ using System; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Microsoft.Recognizers.Text; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs index e015a10e..e7afcf6d 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs @@ -4,15 +4,14 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Xunit; using Xunit.Sdk; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs index 427eeb25..d9a73329 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/PromptValidatorContextTests.cs @@ -5,9 +5,9 @@ using Xunit; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs index da47a9a1..4a0a6b5f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs @@ -3,14 +3,14 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Telemetry; using Moq; using Xunit; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/SkillDialogTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/SkillDialogTests.cs index 68f5691b..f0fe926c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/SkillDialogTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/SkillDialogTests.cs @@ -16,9 +16,9 @@ using Microsoft.Agents.Storage; using System.Text.Json; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.State; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs index 46ed93cf..f6ea6b5e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs @@ -7,8 +7,8 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests @@ -40,7 +40,7 @@ public InterceptTurnContext(TestCloudAdapter testAdapter, ITurnContext innerTurn public IChannelAdapter Adapter => _innerTurnContext.Adapter; - public TurnContextStateCollection TurnState => _innerTurnContext.TurnState; + public ITurnState TurnState => _innerTurnContext.TurnState; public IActivity Activity => _innerTurnContext.Activity; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs index bc8b2f5b..009c077a 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TextPromptTests.cs @@ -4,13 +4,14 @@ using System; using System.IO; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Storage.Transcript; using Microsoft.Agents.Core.Models; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs index 9beaff24..4b2af47d 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/WaterfallTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; @@ -13,6 +12,8 @@ using Microsoft.Recognizers.Text; using Xunit; using Microsoft.Agents.Core; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.Dialogs.Tests { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogAndWelcomeBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogAndWelcomeBot.cs index 1ec1ea21..832f449a 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogAndWelcomeBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogAndWelcomeBot.cs @@ -6,8 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs index b365188a..6504b068 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/DialogBot.cs @@ -4,11 +4,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Bots { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs index 978e7db9..92867d97 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/MyBot.cs @@ -4,8 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Bots diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs index ec0980b3..dcd0b944 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs @@ -8,7 +8,6 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Bots @@ -17,7 +16,7 @@ public class ProactiveBot : ActivityHandler { protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { - var claimsIdentity = turnContext.TurnState.Get(ChannelAdapter.BotIdentityKey) as ClaimsIdentity; + var claimsIdentity = turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) as ClaimsIdentity; var botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim); diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs index 8fd02fec..89edff80 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/DialogExtensions.cs @@ -4,9 +4,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.State; -using Microsoft.Agents.Core.Interfaces; using System; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.TestBot.Shared { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs index 4f3227a3..03cec20c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Dialogs/UserProfileDialog.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.BotBuilder.Dialogs.Choices; -using Microsoft.Agents.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; namespace Microsoft.Agents.BotBuilder.TestBot.Shared.Dialogs diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogAndWelcomeBotTests.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogAndWelcomeBotTests.cs index 352fb584..2177485f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogAndWelcomeBotTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogAndWelcomeBotTests.cs @@ -8,8 +8,8 @@ using Microsoft.BotBuilderSamples.Tests.Framework; using Xunit; using Microsoft.Agents.Storage; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.BotBuilderSamples.Tests.Bots { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogBotTests.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogBotTests.cs index b51bcfff..8832aaed 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogBotTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Bots/DialogBotTests.cs @@ -8,9 +8,9 @@ using Moq; using Xunit; using Microsoft.Agents.Storage; -using Microsoft.Agents.State; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; namespace Microsoft.BotBuilderSamples.Tests.Bots { diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Framework/SimpleMockFactory.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Framework/SimpleMockFactory.cs index d0352ee1..491a2c86 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Framework/SimpleMockFactory.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Tests/Framework/SimpleMockFactory.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Moq; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs index f2aa430a..20ff82e6 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs @@ -2,8 +2,8 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Connector; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Moq; using System; @@ -657,7 +657,7 @@ public async Task TestDelegatingTurnContext() //turnContextMock.Setup(tc => tc.Adapter).Returns(new BotFrameworkAdapter(new SimpleCredentialProvider())); turnContextMock.Setup(tc => tc.Adapter).Returns(new NotImplementedAdapter()); - turnContextMock.Setup(tc => tc.TurnState).Returns(new TurnContextStateCollection()); + turnContextMock.Setup(tc => tc.TurnState).Returns((ITurnState) new TurnState()); turnContextMock.Setup(tc => tc.Responded).Returns(false); turnContextMock.Setup(tc => tc.OnDeleteActivity(It.IsAny())); turnContextMock.Setup(tc => tc.OnSendActivities(It.IsAny())); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/AnonymousReceiveMiddleware.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/AnonymousReceiveMiddleware.cs index 11ee7a84..8f5b8828 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/AnonymousReceiveMiddleware.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/AnonymousReceiveMiddleware.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/CallCountingMiddleware.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/CallCountingMiddleware.cs index 3edad61b..dad263f4 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/CallCountingMiddleware.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/CallCountingMiddleware.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; using System.Threading; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs index bd213057..e86926d4 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterTests.cs index 603c096e..b6d9ac30 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Threading; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/MiddlewareSetTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/MiddlewareSetTests.cs index d4d40650..4bacffc5 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/MiddlewareSetTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/MiddlewareSetTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/NotImplementedAdapter.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/NotImplementedAdapter.cs index 8d8c48a9..0ec06ee7 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/NotImplementedAdapter.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/NotImplementedAdapter.cs @@ -1,6 +1,4 @@ -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Models; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs index e42e5af7..8f615510 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using Xunit; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.SharePoint.Models; using Microsoft.Agents.SharePoint; using Microsoft.Agents.SharePoint.Compat; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs index e506b483..dbe34334 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs @@ -150,7 +150,7 @@ public override TurnContext CreateTurnContext(IActivity activity) claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _parentBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _skillBotId)); - turnContext.TurnState.Add(BotIdentityKey, claimsIdentity); + turnContext.TurnState.Temp.SetValue(BotIdentityKey, claimsIdentity); return turnContext; } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs index 1e966071..9d051d28 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Xunit; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs index 8ec4f14a..0889c53f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Reflection; @@ -44,7 +43,7 @@ public void Constructor_ShouldInstantiateCorrectly() public void TurnContext_ShouldBeClonedCorrectly() { var context1 = new TurnContext(new SimpleAdapter(), new Activity() { Text = "one" }); - context1.TurnState.Add("x", "test"); + context1.TurnState.Temp.SetValue("x", "test"); context1.OnSendActivities((context, activities, next) => next()); context1.OnDeleteActivity((context, activity, next) => next()); context1.OnUpdateActivity((context, activity, next) => next()); @@ -83,14 +82,14 @@ public async Task Responded_ShouldBeTrueAfterReplyingToActivity() public void Get_ThrowsOnNullKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - Assert.Throws(() => context.TurnState.Get(null)); + Assert.Throws(() => context.TurnState.Temp.GetValue(null)); } [Fact] public void Get_ShouldReturnNullOnEmptyKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - var service = context.TurnState.Get(string.Empty); + var service = context.TurnState.Temp.GetValue(string.Empty); Assert.Null(service); } @@ -98,7 +97,7 @@ public void Get_ShouldReturnNullOnEmptyKey() public void Get_ShouldReturnNullWithUnknownKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - var result = context.TurnState.Get("test"); + var result = context.TurnState.Temp.GetValue("test"); Assert.Null(result); } @@ -107,8 +106,8 @@ public void Get_ShouldReturnCachedValueUsingKeyName() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - context.TurnState.Add("bar", "foo"); - var result = context.TurnState.Get("bar"); + context.TurnState.Temp.SetValue("bar", "foo"); + var result = context.TurnState.Temp.GetValue("bar"); Assert.Equal("foo", result); } @@ -118,8 +117,8 @@ public void Get_ShouldReturnGenericValuegUsingTypeAsKeyName() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - context.TurnState.Add("foo"); - string result = context.TurnState.Get(); + context.TurnState.Temp.SetValue("foo"); + string result = context.TurnState.Temp.GetValue(); Assert.Equal("foo", result); } diff --git a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs index af7c9d8b..58636f61 100644 --- a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs @@ -13,8 +13,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector; using Microsoft.Agents.Client.Tests.Logger; -using Microsoft.Agents.Core; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Extensions.Configuration; @@ -79,7 +77,7 @@ public async Task TestSendAndReplyToConversationAsync(string activityType, strin // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); // Assert based on activity type, if (activityType == ActivityTypes.Message) @@ -134,7 +132,7 @@ public async Task TestCommandActivities(string commandActivityType, string name, // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); if (name.StartsWith("application/")) { // Should be sent to the channel and not to the bot. @@ -169,7 +167,7 @@ public async Task TestDeleteActivityAsync() await sut.OnDeleteActivityAsync(mockObjects.CreateTestClaims(), conversationId, activityToDelete); // Assert - Assert.NotNull(mockObjects.TurnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); Assert.Equal(activityToDelete, mockObjects.ActivityIdToDelete); } @@ -188,7 +186,7 @@ public async Task TestUpdateActivityAsync() // Assert Assert.Equal("resourceId", response.Id); - Assert.NotNull(mockObjects.TurnContext.TurnState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); Assert.Equal(activityToUpdate, mockObjects.TurnContext.Activity.Id); Assert.Equal(activity.Text, mockObjects.UpdateActivity.Text); } @@ -310,7 +308,7 @@ private Mock CreateMockAdapter(ILogger logger) { // Create and capture the TurnContext so we can run assertions on it. TurnContext = new TurnContext(adapter.Object, conv.GetContinuationActivity()); - TurnContext.TurnState.Add(Client); + TurnContext.TurnState.Temp.SetValue(Client); await botCallbackHandler(TurnContext, cancel); }); diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs index 89617548..f959252b 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; using Microsoft.Extensions.Configuration; diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs index 68c8a648..095b9f53 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs @@ -12,7 +12,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Connector.Types; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; using Microsoft.AspNetCore.Http; @@ -36,7 +35,7 @@ public void OnTurnError_ShouldSetMiddlewares() { var record = UseRecord(); - Assert.Single(record.Adapter.MiddlewareSet as IEnumerable); + Assert.Single(record.Adapter.MiddlewareSet as IEnumerable); } [Fact] @@ -220,7 +219,7 @@ private static Record UseRecord() var factory = new Mock(); var queue = new Mock(); var logger = new Mock>(); - var middleware = new Mock(); + var middleware = new Mock(); var adapter = new TestAdapter(factory.Object, queue.Object, logger.Object, true, middleware.Object); return new(adapter, factory, queue, logger); @@ -243,8 +242,8 @@ private class TestAdapter( IActivityTaskQueue activityTaskQueue, ILogger logger = null, bool async = true, - params Core.Interfaces.IMiddleware[] middlewares) - : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, async, middlewares) + params BotBuilder.IMiddleware[] middlewares) + : CloudAdapter(channelServiceClientFactory, activityTaskQueue, null, logger, async, middlewares) { public override Task ProcessActivityAsync(ClaimsIdentity claimsIdentity, IActivity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs index 9e37b706..cf337676 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs @@ -8,7 +8,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Client; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; using Microsoft.Agents.Storage; diff --git a/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs index 824de710..b4292cde 100644 --- a/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs index 7f5a9d09..941d3b35 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System; using System.Collections.Generic; @@ -15,7 +18,7 @@ public class BotStateSetTests public void TurnState_Properties() { var storage = new MemoryStorage(); - var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); Assert.IsType(turnState.GetScope(UserState.ScopeName)); Assert.IsType(turnState.GetScope(ConversationState.ScopeName)); @@ -28,7 +31,7 @@ public async Task TurnState_LoadAsync() var turnContext = TestUtilities.CreateEmptyContext(); { - var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); await turnState.LoadStateAsync(turnContext, false); var userCount = turnState.GetValue("user.userCount", () => 0); @@ -46,7 +49,7 @@ public async Task TurnState_LoadAsync() } { - var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); await turnState.LoadStateAsync(turnContext); @@ -60,7 +63,7 @@ public async Task TurnState_LoadAsync() [Fact] public void TurnState_TempState() { - var turnState = new BotStateSet(new TempState()); + var turnState = new TurnState(new TempState()); // Get should create property var count = turnState.Temp.GetValue("count", () => 1); @@ -74,9 +77,8 @@ public void TurnState_TempState() count = turnState.GetValue("temp.count"); Assert.Equal(2, count); - turnState.Temp.AuthScope = "botscope"; - Assert.Equal("botscope", turnState.Temp.AuthScope); - Assert.Equal("botscope", turnState.Temp.GetValue(TempState.AuthScopeKey)); + turnState.SetValue($"temp.{ChannelAdapter.OAuthScopeKey}", "botscope"); + Assert.Equal("botscope", turnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey)); } [Fact] @@ -115,7 +117,7 @@ public async Task TurnState_DottedProperties() var turnContext = TestUtilities.CreateEmptyContext(); { - var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); await turnState.LoadStateAsync(turnContext, false); // Add a couple array elements @@ -181,7 +183,7 @@ public async Task TurnState_ReturnsDefaultForNullValueType() { var storage = new MemoryStorage(); var turnContext = TestUtilities.CreateEmptyContext(); - var turnState = new BotStateSet(new UserState(storage), new ConversationState(storage)); + var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); await turnState.LoadStateAsync(turnContext, false); var userObject = turnState.GetValue("user.userStateObject"); @@ -210,7 +212,7 @@ public async Task TurnState_SaveAsync() // setup convState var convState = new ConversationState(storage); - var turnState = new BotStateSet(userState, convState); + var turnState = new TurnState(userState, convState); var context = TestUtilities.CreateEmptyContext(); await turnState.LoadStateAsync(context); diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs index 77d52ba3..621e07c1 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs @@ -7,8 +7,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.Testing; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using Microsoft.VisualBasic; @@ -52,20 +53,20 @@ public void State_WithDefaultBotState() { var storage = new MemoryStorage(); - var turnState = new BotStateSet(storage); + var turnState = new TurnState(storage); Assert.IsAssignableFrom(turnState.Conversation); Assert.IsAssignableFrom(turnState.User); Assert.IsAssignableFrom(turnState.Temp); Assert.Throws(() => turnState.Private); - turnState = new BotStateSet(storage, new PrivateConversationState(storage)); + turnState = new TurnState(storage, new PrivateConversationState(storage)); Assert.IsAssignableFrom(turnState.Conversation); Assert.IsAssignableFrom(turnState.User); Assert.IsAssignableFrom(turnState.Temp); Assert.IsAssignableFrom(turnState.GetScope()); - turnState = new BotStateSet(new UserState(storage), new ConversationState(storage), new TempState()); + turnState = new TurnState(new UserState(storage), new ConversationState(storage), new TempState()); Assert.IsAssignableFrom(turnState.Conversation); Assert.IsAssignableFrom(turnState.User); Assert.IsAssignableFrom(turnState.Temp); @@ -380,7 +381,7 @@ public async Task State_DoNOTRememberContextState() await new TestFlow(adapter, (context, cancellationToken) => { - var obj = context.TurnState.Get(); + var obj = context.TurnState.Temp.GetValue(); Assert.Null(obj); return Task.CompletedTask; }) diff --git a/src/tests/Microsoft.Agents.State.Tests/Microsoft.Agents.State.Tests.csproj b/src/tests/Microsoft.Agents.State.Tests/Microsoft.Agents.State.Tests.csproj index ca13fefd..0997697f 100644 --- a/src/tests/Microsoft.Agents.State.Tests/Microsoft.Agents.State.Tests.csproj +++ b/src/tests/Microsoft.Agents.State.Tests/Microsoft.Agents.State.Tests.csproj @@ -24,7 +24,6 @@ - diff --git a/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs b/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs index 9bad3cfb..f270b7f5 100644 --- a/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/ObjectPathTests.cs @@ -1,7 +1,7 @@ #pragma warning disable SA1402 +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.State; using System; using System.Collections.Generic; using System.Text.Json; diff --git a/src/tests/Microsoft.Agents.Storage.Tests/TranscriptLoggerMiddlewareTests.cs b/src/tests/Microsoft.Agents.Storage.Tests/TranscriptLoggerMiddlewareTests.cs index 3579c24f..b84b7b51 100644 --- a/src/tests/Microsoft.Agents.Storage.Tests/TranscriptLoggerMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.Storage.Tests/TranscriptLoggerMiddlewareTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Agents.Storage.Tests { public class TranscriptLoggerMiddlewareTests { + /* [Fact] public async Task ShouldNotLogContinueConversation() { @@ -140,5 +141,6 @@ public async Task OnTurnAsync_ShouldGenerateActivityId() Assert.Equal(activity.Text, activities.Items[0].Text); Assert.NotEqual(activity.Id, activities.Items[0].Id); } + */ } } diff --git a/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs b/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs index 0d90a6a6..c634a76c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs @@ -1,5 +1,4 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using System; using System.Threading; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs b/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs index 178f5f5b..986baeb4 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Interfaces; using Microsoft.Agents.Core.Models; using Xunit; diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs b/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs index 4154e3d9..4edb17cf 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs +++ b/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Interfaces; +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Compat; using Microsoft.Agents.Teams.Models; From 7a3da63929b1c3b0ef4bfa010831c06aa00ac423 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 5 Feb 2025 09:24:24 -0600 Subject: [PATCH 13/60] Application using SDK ITurnState --- .../Debugging/DebugSupport.cs | 2 +- .../DialogContext.cs | 7 +- .../DialogExtensions.cs | 28 +- .../Prompts/OAuthPrompt.cs | 8 +- .../Recognizer.cs | 2 +- .../SkillDialog.cs | 4 +- .../{Application => App}/ActivityUtilities.cs | 2 +- .../AdaptiveCardInvokeResponseFactory.cs | 2 +- .../AdaptiveCards/AdaptiveCardsFeature.cs} | 64 +- .../AdaptiveCards/AdaptiveCardsHandlers.cs | 13 +- .../AdaptiveCards/AdaptiveCardsOptions.cs | 2 +- .../AdaptiveCards/Query.cs | 2 +- .../{Application => App}/Application.cs | 97 +-- .../ApplicationBuilder.cs | 28 +- .../ApplicationOptions.cs | 16 +- .../Authentication/AuthException.cs | 2 +- .../Authentication/IAuthentication.cs | 15 +- .../ConversationUpdateEvents.cs | 2 +- .../{Application => App}/HandoffHandler.cs | 7 +- .../MultipleRouteSelector.cs | 2 +- .../{Application => App}/Route.cs | 15 +- .../{Application => App}/State/IMemory.cs | 0 .../{Application => App}/State/ITurnState.cs | 0 .../{Application => App}/State/MemoryFork.cs | 0 .../{Application => App}/State/Record.cs | 0 .../{Application => App}/State/TempState.cs | 0 .../{Application => App}/State/TurnState.cs | 1 - .../State/TurnStateEntry.cs | 0 .../{Application => App}/StreamingResponse.cs | 2 +- .../TurnEventHandlerAsync.cs | 7 +- .../{Application => App}/TypingTimer.cs | 2 +- .../ChannelServiceAdapterBase.cs | 29 +- .../Compat/ActivityHandler.cs | 2 +- .../Compat/BotFrameworkSkillHandler.cs | 10 +- .../Compat/ShowTypingMiddleware.cs | 15 +- .../Compat/TypedTurnContext.cs | 8 +- .../ITurnContext.cs | 7 +- .../Microsoft.Agents.BotBuilder/OAuthFlow.cs | 8 +- .../OAuthTurnStateConstants.cs | 10 - .../State/BotState.cs | 52 +- .../TurnContext.cs | 15 +- .../TurnContextStateCollection.cs | 53 +- .../Microsoft.Agents.State/AssemblyInfo.cs | 7 - .../AutoSaveStateMiddleware.cs | 96 -- .../Core/Microsoft.Agents.State/BotState.cs | 502 ----------- .../Microsoft.Agents.State/BotStateSet.cs | 152 ---- .../ConversationState.cs | 40 - .../Core/Microsoft.Agents.State/IBotState.cs | 25 - .../IPropertyManager.cs | 22 - .../IStatePropertyAccessor.cs | 44 - .../IStatePropertyInfo.cs | 19 - .../Core/Microsoft.Agents.State/ITurnState.cs | 30 - .../Microsoft.Agents.State.csproj | 25 - .../Microsoft.Agents.State/ObjectMerge.cs | 122 --- .../Core/Microsoft.Agents.State/ObjectPath.cs | 820 ------------------ .../PrivateConversationState.cs | 42 - .../Core/Microsoft.Agents.State/README.md | 5 - .../Core/Microsoft.Agents.State/TempState.cs | 93 -- .../Core/Microsoft.Agents.State/UserState.cs | 40 - .../Hosting/AspNetCore/CloudAdapter.cs | 3 +- .../SharePointSSOTokenExchangeMiddleware.cs | 2 +- .../ConfigHandlerAsync.cs | 7 +- .../{Application => App}/FeedbackLoopData.cs | 2 +- .../FeedbackLoopHandler.cs | 7 +- .../FileConsentCardHandler.cs | 7 +- .../IInputFileDownloader.cs | 9 +- .../{Application => App}/InputFile.cs | 2 +- .../Meetings/MeetingsFeature.cs} | 29 +- .../Meetings/MeetingsHandlers.cs | 13 +- .../MessageExtensionsFeature.cs} | 112 ++- .../MessageExtensionsHandlers.cs | 36 +- .../O365ConnectorCardActionHandler.cs | 7 +- .../ReadReceiptHandler.cs | 7 +- .../TaskModules/TaskModulesFeature.cs} | 38 +- .../TaskModules/TaskModulesHandlers.cs | 10 +- .../TaskModules/TaskModulesOptions.cs | 2 +- .../{Application => App}/TeamsApplication.cs | 76 +- .../TeamsConversationUpdateEvents.cs | 4 +- .../Compat/TeamsSSOTokenExchangeMiddleware.cs | 2 +- .../Connector/TeamsInfo.cs | 2 +- .../messaging.echoBot/EchoBotApplication.cs | 25 +- .../messaging.echoBot/Model/AppState.cs | 53 -- .../Application/messaging.echoBot/Program.cs | 13 +- .../messaging.echoBot/StateExtensions.cs | 13 +- src/samples/BotToBot/Bot1/Bots/Bot1.cs | 2 +- .../Dialogs/LogoutDialog.cs | 2 +- .../Program.cs | 2 +- .../bot-tag-mention/Dialogs/LogoutDialog.cs | 2 +- .../Dialogs/LogoutDialog.cs | 2 +- .../Adapters/TestAdapter.cs | 2 +- .../XUnit/XUnitDialogTestLogger.cs | 4 +- .../DialogExtensionsTests.cs | 8 +- .../TestCloudAdapter.cs | 3 +- .../Bots/ProactiveBot.cs | 2 +- .../ActivityHandlerTests.cs | 6 +- .../ShowTypingMiddlewareTests.cs | 2 +- .../TurnContextTests.cs | 18 +- .../BotFrameworkSkillHandlerTests.cs | 10 +- .../CloudAdapterTests.cs | 2 +- .../{BotStateTests.cs => BotSateTests.cs} | 3 +- ...{BotStateSetTests.cs => TurnStateTests.cs} | 6 +- 101 files changed, 476 insertions(+), 2706 deletions(-) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/ActivityUtilities.cs (91%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs (96%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application/AdaptiveCards/AdaptiveCards.cs => App/AdaptiveCards/AdaptiveCardsFeature.cs} (87%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/AdaptiveCards/AdaptiveCardsHandlers.cs (79%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/AdaptiveCards/AdaptiveCardsOptions.cs (91%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/AdaptiveCards/Query.cs (95%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/Application.cs (88%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/ApplicationBuilder.cs (76%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/ApplicationOptions.cs (80%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/Authentication/AuthException.cs (95%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/Authentication/IAuthentication.cs (84%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/ConversationUpdateEvents.cs (91%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/HandoffHandler.cs (64%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/MultipleRouteSelector.cs (93%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/Route.cs (73%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/IMemory.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/ITurnState.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/MemoryFork.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/Record.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/TempState.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/TurnState.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/State/TurnStateEntry.cs (100%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/StreamingResponse.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/TurnEventHandlerAsync.cs (80%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Application => App}/TypingTimer.cs (98%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Compat => }/TurnContextStateCollection.cs (72%) delete mode 100644 src/libraries/Core/Microsoft.Agents.State/AssemblyInfo.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/BotState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/ConversationState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IBotState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/ITurnState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/Microsoft.Agents.State.csproj delete mode 100644 src/libraries/Core/Microsoft.Agents.State/ObjectMerge.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/README.md delete mode 100644 src/libraries/Core/Microsoft.Agents.State/TempState.cs delete mode 100644 src/libraries/Core/Microsoft.Agents.State/UserState.cs rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/ConfigHandlerAsync.cs (61%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/FeedbackLoopData.cs (95%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/FeedbackLoopHandler.cs (61%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/FileConsentCardHandler.cs (65%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/IInputFileDownloader.cs (67%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/InputFile.cs (95%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application/Meetings/Meetings.cs => App/Meetings/MeetingsFeature.cs} (81%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/Meetings/MeetingsHandlers.cs (64%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application/MessageExtensions/MessageExtensions.cs => App/MessageExtensions/MessageExtensionsFeature.cs} (86%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/MessageExtensions/MessageExtensionsHandlers.cs (68%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/O365ConnectorCardActionHandler.cs (63%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/ReadReceiptHandler.cs (61%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application/TaskModules/TaskModules.cs => App/TaskModules/TaskModulesFeature.cs} (87%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/TaskModules/TaskModulesHandlers.cs (63%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/TaskModules/TaskModulesOptions.cs (90%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/TeamsApplication.cs (84%) rename src/libraries/Partner/Microsoft.Agents.Teams/{Application => App}/TeamsConversationUpdateEvents.cs (95%) delete mode 100644 src/samples/Application/messaging.echoBot/Model/AppState.cs rename src/tests/Microsoft.Agents.State.Tests/{BotStateTests.cs => BotSateTests.cs} (99%) rename src/tests/Microsoft.Agents.State.Tests/{BotStateSetTests.cs => TurnStateTests.cs} (99%) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs index 7ba68560..07f83774 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Debugging/DebugSupport.cs @@ -23,7 +23,7 @@ public static class DebugSupport /// turnContext. /// IDialogDebugger. public static IDialogDebugger GetDebugger(this ITurnContext context) => - context.TurnState.Temp.GetValue() ?? NullDialogDebugger.Instance; + context.Services.Get() ?? NullDialogDebugger.Instance; /// /// Extension method to get IDialogDebugger from DialogContext. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs index f5fd36ec..78fb1cd7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogContext.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.Dialogs.Debugging; using Microsoft.Agents.BotBuilder.State; @@ -242,9 +241,9 @@ public DialogInstance ActiveDialog { // if we are continuing and haven't emitted the activityReceived event, emit it // NOTE: This is backward compatible way for activity received to be fired even if you have legacy dialog loop - if (!Context.TurnState.Temp.HasValue("activityReceivedEmitted")) + if (!Context.StackState.Has("activityReceivedEmitted")) { - Context.TurnState.Temp.SetValue("activityReceivedEmitted", true); + Context.StackState.Set("activityReceivedEmitted", true); // Dispatch "activityReceived" event // - This will queue up any interruptions. @@ -474,7 +473,7 @@ public DialogInstance ActiveDialog // End the current dialog and giving the reason. await EndActiveDialogAsync(DialogReason.ReplaceCalled, cancellationToken: cancellationToken).ConfigureAwait(false); - Context.TurnState.Temp.SetValue("turn.__repeatDialogId", dialogId); + Context.StackState.Set("turn.__repeatDialogId", dialogId); // Start replacement dialog return await BeginDialogAsync(dialogId, options, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index 15787890..e34adbef 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -41,7 +41,7 @@ public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, var dialogSet = new DialogSet(dialogState); // look for the IBotTelemetryClient on the TurnState, if not there take it from the Dialog, if not there fall back to the "null" default - dialogSet.TelemetryClient = turnContext.TurnState.Temp.GetValue() ?? dialog.TelemetryClient ?? NullBotTelemetryClient.Instance; + dialogSet.TelemetryClient = turnContext.Services.Get() ?? dialog.TelemetryClient ?? NullBotTelemetryClient.Instance; dialogSet.Add(dialog); @@ -50,6 +50,24 @@ public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, await InternalRunAsync(turnContext, dialog.Id, dialogContext, cancellationToken).ConfigureAwait(false); } + [Obsolete("Use the non-IStatePropertyAccessor version")] + public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor accessor, CancellationToken cancellationToken) + { + var state = await accessor.GetAsync(turnContext, () => { return new DialogState(); }, cancellationToken).ConfigureAwait(false); + + var dialogSet = new DialogSet(state); + + // look for the IBotTelemetryClient on the TurnState, if not there take it from the Dialog, if not there fall back to the "null" default + dialogSet.TelemetryClient = turnContext.Services.Get() ?? dialog.TelemetryClient ?? NullBotTelemetryClient.Instance; + + dialogSet.Add(dialog); + + var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false); + + await InternalRunAsync(turnContext, dialog.Id, dialogContext, cancellationToken).ConfigureAwait(false); + } + + internal static async Task InternalRunAsync(ITurnContext turnContext, string dialogId, DialogContext dialogContext, CancellationToken cancellationToken) { DialogTurnResult dialogTurnResult = null; @@ -164,11 +182,11 @@ private static async Task InnerRunAsync(ITurnContext turnConte /// private static bool SendEoCToParent(ITurnContext turnContext) { - if (turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims)) + if (turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims)) { // EoC Activities returned by skills are bounced back to the bot by SkillHandler. // In those cases we will have a SkillConversationReference instance in state. - var skillConversationReference = turnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey); + var skillConversationReference = turnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey); if (skillConversationReference != null) { // If the skillConversationReference.OAuthScope is for one of the supported channels, we are at the root and we should not send an EoC. @@ -183,12 +201,12 @@ private static bool SendEoCToParent(ITurnContext turnContext) private static bool IsFromParentToSkill(ITurnContext turnContext) { - if (turnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey) != null) + if (turnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey) != null) { return false; } - return turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); + return turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); } // Recursively walk up the DC stack to find the active DC. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index eb87ee7e..4337e2ab 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -197,10 +197,10 @@ public override async Task ContinueDialogAsync(DialogContext d // recreate a ConnectorClient and set it in TurnState so replies use the correct one var serviceUrl = dc.Context.Activity.ServiceUrl; - var claimsIdentity = dc.Context.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey); + var claimsIdentity = dc.Context.StackState.Get(ChannelAdapter.BotIdentityKey); var audience = callerInfo.Scope; var connectorClient = await CreateConnectorClientAsync(dc.Context, serviceUrl, claimsIdentity, audience, cancellationToken).ConfigureAwait(false); - dc.Context.TurnState.Temp.SetValue(connectorClient); + dc.Context.Services.Set(connectorClient); } } @@ -278,7 +278,7 @@ public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken c public static async Task CreateConnectorClientAsync(ITurnContext turnContext, string serviceUrl, ClaimsIdentity claimsIdentity, string audience, CancellationToken cancellationToken) { - var clientFactory = turnContext.TurnState.Temp.GetValue(); + var clientFactory = turnContext.Services.Get(); if (clientFactory != null) { return await clientFactory.CreateConnectorClientAsync(claimsIdentity, serviceUrl, audience, cancellationToken).ConfigureAwait(false); @@ -294,7 +294,7 @@ private static bool IsTokenResponseEvent(ITurnContext turnContext) private static CallerInfo CreateCallerInfo(ITurnContext turnContext) { - if (turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && BotClaims.IsBotClaim(botIdentity.Claims)) + if (turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && BotClaims.IsBotClaim(botIdentity.Claims)) { return new CallerInfo { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs index d430448a..213659c8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Recognizer.cs @@ -212,7 +212,7 @@ protected void TrackRecognizerResult(DialogContext dialogContext, string eventNa { if (TelemetryClient is NullBotTelemetryClient) { - var turnStateTelemetryClient = dialogContext.Context.TurnState.Temp.GetValue(); + var turnStateTelemetryClient = dialogContext.Context.Services.Get(); TelemetryClient = turnStateTelemetryClient ?? TelemetryClient; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs index 977bf5e8..4795e3a6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs @@ -310,7 +310,7 @@ private async Task SendToSkillAsync(ITurnContext context, IActivity a /// private async Task InterceptOAuthCardsAsync(ITurnContext turnContext, IActivity activity, string connectionName, CancellationToken cancellationToken) { - var userTokenClient = turnContext.TurnState.Temp.GetValue(); + var userTokenClient = turnContext.Services.Get(); if (string.IsNullOrWhiteSpace(connectionName) || userTokenClient == null) { // The adapter may choose not to support token exchange, in which case we fallback to showing an oauth card to the user. @@ -377,7 +377,7 @@ private async Task CreateSkillConversationIdAsync(ITurnContext context, // Create a conversationId to interact with the skill and send the activity var conversationIdFactoryOptions = new ConversationIdFactoryOptions { - FromBotOAuthScope = context.TurnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = context.StackState.Get(ChannelAdapter.OAuthScopeKey), FromBotId = DialogOptions.BotId, Activity = activity, Bot = DialogOptions.Skill diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs similarity index 91% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs index bff81ad2..01a8c406 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ActivityUtilities.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Net; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { public static class ActivityUtilities { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs similarity index 96% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs index 5b8c8b4c..7534d277 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardInvokeResponseFactory.cs @@ -4,7 +4,7 @@ using AdaptiveCards; using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards +namespace Microsoft.Agents.BotBuilder.App.AdaptiveCards { /// /// Contains utility methods for creating various types of . diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs similarity index 87% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index 8ac54344..1793a705 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCards.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; @@ -10,7 +10,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards +namespace Microsoft.Agents.BotBuilder.App.AdaptiveCards { /// /// Constants for adaptive card invoke names @@ -26,21 +26,19 @@ public class AdaptiveCardsInvokeNames /// /// AdaptiveCards class to enable fluent style registration of handlers related to Adaptive Cards. /// - /// The type of the turn state object used by the application. - public class AdaptiveCards - where TState : TurnState, new() + public class AdaptiveCardsFeature { private static readonly string ACTION_EXECUTE_TYPE = "Action.Execute"; private static readonly string SEARCH_INVOKE_NAME = "application/search"; private static readonly string DEFAULT_ACTION_SUBMIT_FILTER = "verb"; - private readonly Application _app; + private readonly Application _app; /// /// Creates a new instance of the AdaptiveCards class. /// /// The top level application class to register handlers with. - public AdaptiveCards(Application app) + public AdaptiveCardsFeature(Application app) { this._app = app; } @@ -51,7 +49,7 @@ public AdaptiveCards(Application app) /// The named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handler) + public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handler) { ArgumentException.ThrowIfNullOrWhiteSpace(verb); ArgumentNullException.ThrowIfNull(handler); @@ -65,7 +63,7 @@ public Application OnActionExecute(string verb, ActionExecuteHandlerAsyn /// Regular expression to match against the named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) + public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -79,11 +77,11 @@ public Application OnActionExecute(Regex verbPattern, ActionExecuteHandl /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) + public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { AdaptiveCardInvokeValue? invokeValue; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -109,7 +107,7 @@ public Application OnActionExecute(RouteSelectorAsync routeSelector, Act /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) + public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -160,7 +158,7 @@ public Application OnActionExecute(MultipleRouteSelector routeSelectors, /// The named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(string verb, ActionSubmitHandler handler) + public Application OnActionSubmit(string verb, ActionSubmitHandler handler) { ArgumentException.ThrowIfNullOrWhiteSpace(verb); ArgumentNullException.ThrowIfNull(handler); @@ -192,7 +190,7 @@ public Application OnActionSubmit(string verb, ActionSubmitHandlerRegular expression to match against the named action to be handled. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) + public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -224,11 +222,11 @@ public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) + public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(turnContext.Activity.Text) @@ -266,7 +264,7 @@ public Application OnActionSubmit(RouteSelectorAsync routeSelector, Acti /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) + public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -300,7 +298,7 @@ public Application OnActionSubmit(MultipleRouteSelector routeSelectors, /// The dataset to be searched. /// Function to call when the search is triggered. /// The application instance for chaining purposes. - public Application OnSearch(string dataset, SearchHandlerAsync handler) + public Application OnSearch(string dataset, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(dataset); ArgumentNullException.ThrowIfNull(handler); @@ -314,7 +312,7 @@ public Application OnSearch(string dataset, SearchHandlerAsync h /// Regular expression to match against the dataset to be searched. /// Function to call when the search is triggered. /// The application instance for chaining purposes. - public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) + public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(datasetPattern); ArgumentNullException.ThrowIfNull(handler); @@ -328,11 +326,11 @@ public Application OnSearch(Regex datasetPattern, SearchHandlerAsyncFunction that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) + public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { AdaptiveCardSearchInvokeValue? searchInvokeValue; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -346,21 +344,17 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand Query query = new(searchInvokeValue.QueryOptions.Top, searchInvokeValue.QueryOptions.Skip, adaptiveCardsSearchParams); IList results = await handler(turnContext, turnState, query, cancellationToken); - // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + SearchInvokeResponse searchInvokeResponse = new() { - SearchInvokeResponse searchInvokeResponse = new() + StatusCode = 200, + Type = "application/vnd.microsoft.search.searchResponse", + Value = new AdaptiveCardsSearchInvokeResponseValue { - StatusCode = 200, - Type = "application/vnd.microsoft.search.searchResponse", - Value = new AdaptiveCardsSearchInvokeResponseValue - { - Results = results - } - }; - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(searchInvokeResponse); - await turnContext.SendActivityAsync(activity, cancellationToken); - } + Results = results + } + }; + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(searchInvokeResponse); + await turnContext.SendActivityAsync(activity, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: true); return _app; @@ -372,7 +366,7 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHand /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) + public Application OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsHandlers.cs similarity index 79% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsHandlers.cs index 9c3ea931..9841e668 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsHandlers.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsHandlers.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards +namespace Microsoft.Agents.BotBuilder.App.AdaptiveCards { /// /// Parameters passed to AdaptiveCards.OnSearch() handler. @@ -66,36 +66,33 @@ public AdaptiveCardsSearchResult(string title, string value) /// /// Function for handling Adaptive Card Action.Execute events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The data associated with the action. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of AdaptiveCardInvokeResponse, which can be created using . - public delegate Task ActionExecuteHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task ActionExecuteHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken); /// /// Function for handling Adaptive Card Action.Submit events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The data associated with the action. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task ActionSubmitHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task ActionSubmitHandler(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken); /// /// Function for handling Adaptive Card dynamic search events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The query arguments. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A list of AdaptiveCardsSearchResult. - public delegate Task> SearchHandlerAsync(ITurnContext turnContext, TState turnState, Query query, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task> SearchHandlerAsync(ITurnContext turnContext, ITurnState turnState, Query query, CancellationToken cancellationToken); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsOptions.cs similarity index 91% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsOptions.cs index 73d5981c..ddf158ac 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/AdaptiveCardsOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards +namespace Microsoft.Agents.BotBuilder.App.AdaptiveCards { /// /// Options for AdaptiveCards class. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/Query.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/Query.cs index b4e43fae..f6af4eac 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/AdaptiveCards/Query.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/Query.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.BotBuilder.Application.AdaptiveCards +namespace Microsoft.Agents.BotBuilder.App.AdaptiveCards { /// /// Query arguments for Message Extension query and Adaptive Card dynamic search. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs similarity index 88% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 08ac5fa8..010a970f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System; @@ -11,14 +11,12 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Application class for routing and processing incoming requests. /// - /// Type of the turnState. This allows for strongly typed access to the turn turnState. - public class Application : IBot - where TState : TurnState, new() + public class Application : IBot { //TODO //private readonly AuthenticationManager? _authentication; @@ -27,10 +25,10 @@ public class Application : IBot private TypingTimer? _typingTimer; // TODO: These really aren't queues, so why this type? - private readonly ConcurrentQueue> _invokeRoutes; - private readonly ConcurrentQueue> _routes; - private readonly ConcurrentQueue> _beforeTurn; - private readonly ConcurrentQueue> _afterTurn; + private readonly ConcurrentQueue _invokeRoutes; + private readonly ConcurrentQueue _routes; + private readonly ConcurrentQueue _beforeTurn; + private readonly ConcurrentQueue _afterTurn; // TODO //private readonly SelectorAsync? _startSignIn; @@ -40,7 +38,7 @@ public class Application : IBot /// /// Optional. Options used to configure the application. /// - public Application(ApplicationOptions options) + public Application(ApplicationOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -49,15 +47,15 @@ public Application(ApplicationOptions options) if (Options.TurnStateFactory == null) { - this.Options.TurnStateFactory = () => new TState(); + Options.TurnStateFactory = () => new TurnState(); } - AdaptiveCards = new AdaptiveCards(this); + AdaptiveCards = new AdaptiveCardsFeature(this); - _routes = new ConcurrentQueue>(); - _invokeRoutes = new ConcurrentQueue>(); - _beforeTurn = new ConcurrentQueue>(); - _afterTurn = new ConcurrentQueue>(); + _routes = new ConcurrentQueue(); + _invokeRoutes = new ConcurrentQueue(); + _beforeTurn = new ConcurrentQueue(); + _afterTurn = new ConcurrentQueue(); //TODO /* @@ -80,7 +78,7 @@ public Application(ApplicationOptions options) /// /// Fluent interface for accessing Adaptive Card specific features. /// - public AdaptiveCards AdaptiveCards { get; } + public AdaptiveCardsFeature AdaptiveCards { get; } //TODO /* @@ -105,7 +103,7 @@ public AuthenticationManager Authentication /// /// The application's configured options. /// - public ApplicationOptions Options { get; } + public ApplicationOptions Options { get; } /// /// Adds a new route to the application. @@ -123,11 +121,11 @@ public AuthenticationManager Authentication /// Function to call when the route is triggered. /// Boolean indicating if the RouteSelectorAsync is for an activity that uses "invoke" which require special handling. Defaults to `false`. /// The application instance for chaining purposes. - public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) + public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) { ArgumentNullException.ThrowIfNull(selector); ArgumentNullException.ThrowIfNull(handler); - Route route = new(selector, handler, isInvokeRoute); + Route route = new(selector, handler, isInvokeRoute); if (isInvokeRoute) { _invokeRoutes.Enqueue(route); @@ -145,7 +143,7 @@ public Application AddRoute(RouteSelectorAsync selector, RouteHandlerName of the activity type to match. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActivity(string type, RouteHandler handler) + public Application OnActivity(string type, RouteHandler handler) { ArgumentException.ThrowIfNullOrWhiteSpace(type); ArgumentNullException.ThrowIfNull(handler); @@ -160,7 +158,7 @@ public Application OnActivity(string type, RouteHandler handler) /// Regular expression to match against the incoming activity type. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActivity(Regex typePattern, RouteHandler handler) + public Application OnActivity(Regex typePattern, RouteHandler handler) { ArgumentNullException.ThrowIfNull(typePattern); ArgumentNullException.ThrowIfNull(handler); @@ -175,7 +173,7 @@ public Application OnActivity(Regex typePattern, RouteHandler ha /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler) + public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -189,7 +187,7 @@ public Application OnActivity(RouteSelectorAsync routeSelector, RouteHan /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler) + public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -223,7 +221,7 @@ public Application OnActivity(MultipleRouteSelector routeSelectors, Rout /// Name of the conversation update event to handle, can use . /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public virtual Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + public virtual Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) { ArgumentException.ThrowIfNullOrWhiteSpace(conversationUpdateEvent); ArgumentNullException.ThrowIfNull(handler); @@ -270,7 +268,7 @@ public virtual Application OnConversationUpdate(string conversationUpdat /// Name of the conversation update events to handle, can use as array item. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler) + public Application OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler) { ArgumentNullException.ThrowIfNull(conversationUpdateEvents); ArgumentNullException.ThrowIfNull(handler); @@ -294,7 +292,7 @@ public Application OnConversationUpdate(string[] conversationUpdateEvent /// Substring of the incoming message text. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessage(string text, RouteHandler handler) + public Application OnMessage(string text, RouteHandler handler) { ArgumentException.ThrowIfNullOrWhiteSpace(text); ArgumentNullException.ThrowIfNull(handler); @@ -322,7 +320,7 @@ public Application OnMessage(string text, RouteHandler handler) /// Regular expression to match against the text of an incoming message. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessage(Regex textPattern, RouteHandler handler) + public Application OnMessage(Regex textPattern, RouteHandler handler) { ArgumentNullException.ThrowIfNull(textPattern); ArgumentNullException.ThrowIfNull(handler); @@ -346,7 +344,7 @@ public Application OnMessage(Regex textPattern, RouteHandler han /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler) + public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -363,7 +361,7 @@ public Application OnMessage(RouteSelectorAsync routeSelector, RouteHand /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler) + public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -396,7 +394,7 @@ public Application OnMessage(MultipleRouteSelector routeSelectors, Route /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessageReactionsAdded(RouteHandler handler) + public Application OnMessageReactionsAdded(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -414,7 +412,7 @@ public Application OnMessageReactionsAdded(RouteHandler handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnMessageReactionsRemoved(RouteHandler handler) + public Application OnMessageReactionsRemoved(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -432,7 +430,7 @@ public Application OnMessageReactionsRemoved(RouteHandler handle /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnHandoff(HandoffHandler handler) + public Application OnHandoff(HandoffHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -440,17 +438,13 @@ public Application OnHandoff(HandoffHandler handler) string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(context.Activity?.Name, "handoff/action") ); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { string token = turnContext.Activity.Value.GetType().GetProperty("Continuation").GetValue(turnContext.Activity.Value) as string ?? ""; await handler(turnContext, turnState, token, cancellationToken); - // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) - { - Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); - await turnContext.SendActivityAsync(activity, cancellationToken); - } + Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); + await turnContext.SendActivityAsync(activity, cancellationToken); }; AddRoute(routeSelector, routeHandler, isInvokeRoute: true); return this; @@ -467,7 +461,7 @@ public Application OnHandoff(HandoffHandler handler) /// /// Function to call before turn execution. /// The application instance for chaining purposes. - public Application OnBeforeTurn(TurnEventHandlerAsync handler) + public Application OnBeforeTurn(TurnEventHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); _beforeTurn.Enqueue(handler); @@ -482,7 +476,7 @@ public Application OnBeforeTurn(TurnEventHandlerAsync handler) /// /// Function to call after turn execution. /// The application instance for chaining purposes. - public Application OnAfterTurn(TurnEventHandlerAsync handler) + public Application OnAfterTurn(TurnEventHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); _afterTurn.Enqueue(handler); @@ -568,10 +562,9 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } // Load turn state - TState turnState = Options.TurnStateFactory!(); - IStorage? storage = Options.Storage; + ITurnState turnState = Options.TurnStateFactory!(); - await turnState!.LoadStateAsync(storage, turnContext); + await turnState!.LoadStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); //TODO /* @@ -613,14 +606,14 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc */ // Call before turn handler - foreach (TurnEventHandlerAsync beforeTurnHandler in _beforeTurn) + foreach (TurnEventHandlerAsync beforeTurnHandler in _beforeTurn) { if (!await beforeTurnHandler(turnContext, turnState, cancellationToken)) { // Save turn state // - This lets the bot keep track of why it ended the previous turn. It also // allows the dialog system to be used before the AI system is called. - await turnState!.SaveStateAsync(turnContext, storage); + await turnState!.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); return; } @@ -642,7 +635,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // Invoke Activities from Teams need to be responded to in less than 5 seconds. if (ActivityTypes.Invoke.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { - foreach (Route route in _invokeRoutes) + foreach (Route route in _invokeRoutes) { if (await route.Selector(turnContext, cancellationToken)) { @@ -656,7 +649,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // All other ActivityTypes and any unhandled Invokes are run through the remaining routes. if (!eventHandlerCalled) { - foreach (Route route in _routes) + foreach (Route route in _routes) { if (await route.Selector(turnContext, cancellationToken)) { @@ -668,7 +661,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } // Call after turn handler - foreach (TurnEventHandlerAsync afterTurnHandler in _afterTurn) + foreach (TurnEventHandlerAsync afterTurnHandler in _afterTurn) { if (!await afterTurnHandler(turnContext, turnState, cancellationToken)) { @@ -676,7 +669,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } } - await turnState!.SaveStateAsync(turnContext, storage); + await turnState!.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); } finally { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs similarity index 76% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs index f4553140..827a36aa 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationBuilder.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs @@ -4,29 +4,27 @@ using Microsoft.Extensions.Logging; using System; using Microsoft.Agents.Storage; -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// A builder class for simplifying the creation of an Application instance. /// - /// Optional. Type of the turn state. This allows for strongly typed access to the turn state. - public class ApplicationBuilder - where TState : TurnState, IMemory, new() + public class ApplicationBuilder { /// /// The application's configured options. /// - public ApplicationOptions Options { get; } = new(); + public ApplicationOptions Options { get; } = new(); /// /// Configures the storage system to use for storing the bot's state. /// /// The storage system to use. /// The ApplicationBuilder instance. - public ApplicationBuilder WithStorage(IStorage storage) + public ApplicationBuilder WithStorage(IStorage storage) { Options.Storage = storage; return this; @@ -37,7 +35,7 @@ public ApplicationBuilder WithStorage(IStorage storage) /// /// The turn state factory to use. /// The ApplicationBuilder instance. - public ApplicationBuilder WithTurnStateFactory(Func turnStateFactory) + public ApplicationBuilder WithTurnStateFactory(Func turnStateFactory) { Options.TurnStateFactory = turnStateFactory; return this; @@ -48,7 +46,7 @@ public ApplicationBuilder WithTurnStateFactory(Func turnStateFac /// /// The Logger factory /// The ApplicationBuilder instance. - public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) { Options.LoggerFactory = loggerFactory; return this; @@ -59,7 +57,7 @@ public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory /// /// The options for Adaptive Cards. /// The ApplicationBuilder instance. - public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveCardOptions) + public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveCardOptions) { Options.AdaptiveCards = adaptiveCardOptions; return this; @@ -71,7 +69,7 @@ public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions a /// /// The boolean for removing recipient mentions. /// The ApplicationBuilder instance. - public ApplicationBuilder SetRemoveRecipientMention(bool removeRecipientMention) + public ApplicationBuilder SetRemoveRecipientMention(bool removeRecipientMention) { Options.RemoveRecipientMention = removeRecipientMention; return this; @@ -83,7 +81,7 @@ public ApplicationBuilder SetRemoveRecipientMention(bool removeRecipient /// /// The boolean for starting the typing timer. /// The ApplicationBuilder instance. - public ApplicationBuilder SetStartTypingTimer(bool startTypingTimer) + public ApplicationBuilder SetStartTypingTimer(bool startTypingTimer) { Options.StartTypingTimer = startTypingTimer; return this; @@ -109,9 +107,9 @@ public ApplicationBuilder WithAuthentication(ChannelAdapter adapter, Aut /// Builds and returns a new Application instance. /// /// The Application instance. - public Application Build() + public Application Build() { - return new Application(Options); + return new Application(Options); } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs similarity index 80% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index 147440cf..30ad816f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -4,17 +4,15 @@ using Microsoft.Extensions.Logging; using System; using Microsoft.Agents.Storage; -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// - /// Options for the class. + /// Options for the class. /// - /// Type of the turn state. - public class ApplicationOptions - where TState : TurnState, new() + public class ApplicationOptions { /// /// Optional. Storage provider to use for the application. @@ -29,7 +27,7 @@ public class ApplicationOptions /// /// Optional. Factory used to create a custom turn state instance. /// - public Func? TurnStateFactory { get; set; } + public Func? TurnStateFactory { get; set; } /// /// Optional. Logger factory that will be used in this application. @@ -59,4 +57,4 @@ public class ApplicationOptions public AuthenticationOptions? Authentication { get; set; } */ } -} +} \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs index e422db52..dad6839a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/AuthException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.BotBuilder.Application.Authentication +namespace Microsoft.Agents.BotBuilder.App.Authentication { /// /// Cause of user authentication exception. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs similarity index 84% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs index e6ee1311..37d310d1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Authentication/IAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.Authentication +namespace Microsoft.Agents.BotBuilder.App.Authentication { /// /// The sign-in status @@ -62,8 +62,7 @@ public SignInResponse(SignInStatus status) /// /// Handles user sign-in and sign-out. /// - public interface IAuthentication - where TState : TurnState, new() + public interface IAuthentication { /// /// Signs in a user. @@ -73,7 +72,7 @@ public interface IAuthentication /// Application state. /// The cancellation token /// The authentication token if user is signed in. Otherwise returns null. In that case the bot will attempt to sign the user in. - Task SignInUserAsync(ITurnContext context, TState state, CancellationToken cancellationToken = default); + Task SignInUserAsync(ITurnContext context, ITurnState state, CancellationToken cancellationToken = default); /// /// Signs out a user. @@ -81,21 +80,21 @@ public interface IAuthentication /// Current turn context. /// Application state. /// The cancellation token - Task SignOutUserAsync(ITurnContext context, TState state, CancellationToken cancellationToken = default); + Task SignOutUserAsync(ITurnContext context, ITurnState state, CancellationToken cancellationToken = default); /// /// The handler function is called when the user has successfully signed in /// /// The handler function to call when the user has successfully signed in /// The class itself for chaining purpose - IAuthentication OnUserSignInSuccess(Func handler); + IAuthentication OnUserSignInSuccess(Func handler); /// /// The handler function is called when the user sign in flow fails /// /// The handler function to call when the user failed to signed in /// The class itself for chaining purpose - IAuthentication OnUserSignInFailure(Func handler); + IAuthentication OnUserSignInFailure(Func handler); /// /// Check if the user is signed, if they are then return the token. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateEvents.cs similarity index 91% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateEvents.cs index 8c4a5ec7..ea4e3602 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/ConversationUpdateEvents.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateEvents.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Conversation update events. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/HandoffHandler.cs similarity index 64% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/HandoffHandler.cs index a3cecfbe..73ab1acf 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/HandoffHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/HandoffHandler.cs @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Function for handling handoff activities. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The continuation token. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task HandoffHandler(ITurnContext turnContext, TState turnState, string continuation, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task HandoffHandler(ITurnContext turnContext, ITurnState turnState, string continuation, CancellationToken cancellationToken); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/MultipleRouteSelector.cs similarity index 93% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/MultipleRouteSelector.cs index 2b0c7d7f..643345ef 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/MultipleRouteSelector.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/MultipleRouteSelector.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Combination of String, Regex, and RouteSelectorAsync selectors. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Route.cs similarity index 73% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Route.cs index 44c00ce5..5c03dd69 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/Route.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Route.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Function for selecting whether a route handler should be triggered. @@ -19,15 +19,14 @@ namespace Microsoft.Agents.BotBuilder.Application /// /// The common route handler. Function for handling an incoming request. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// - public delegate Task RouteHandler(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task RouteHandler(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken); - internal class Route where TState : TurnState + internal class Route { public Route(RouteSelectorAsync selector, bool isInvokeRoute = false) { @@ -36,14 +35,14 @@ public Route(RouteSelectorAsync selector, bool isInvokeRoute = false) IsInvokeRoute = isInvokeRoute; } - public Route(RouteHandler handler, bool isInvokeRoute = false) + public Route(RouteHandler handler, bool isInvokeRoute = false) { Selector = (_, _) => Task.FromResult(true); Handler = handler; IsInvokeRoute = isInvokeRoute; } - public Route(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) + public Route(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) { Selector = selector; Handler = handler; @@ -52,7 +51,7 @@ public Route(RouteSelectorAsync selector, RouteHandler handler, bool isI public RouteSelectorAsync Selector { get; private set; } - public RouteHandler Handler { get; private set; } + public RouteHandler Handler { get; private set; } public bool IsInvokeRoute { get; private set; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/IMemory.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/ITurnState.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/MemoryFork.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/Record.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TempState.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs index 3ecc4327..0817feb9 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.Storage; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs similarity index 100% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/State/TurnStateEntry.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs index eb8f3840..bf3376c7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/StreamingResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TurnEventHandlerAsync.cs similarity index 80% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TurnEventHandlerAsync.cs index d4c38b17..b1afefa5 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TurnEventHandlerAsync.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TurnEventHandlerAsync.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Turn event handler to do something before or after a turn is run. Returning false from @@ -18,11 +18,10 @@ namespace Microsoft.Agents.BotBuilder.Application /// a way to call into the dialog system. For example, you could use the OAuthPrompt to sign the /// user in before allowing the AI system to run. /// - /// /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// True to continue execution of the current turn. Otherwise, False. - public delegate Task TurnEventHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task TurnEventHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs similarity index 98% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index aedf2f55..059f0a34 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Application/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application +namespace Microsoft.Agents.BotBuilder.App { /// /// Encapsulates the logic for sending "typing" activity to the user. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index 5415ee27..e1af86cd 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -23,11 +23,9 @@ namespace Microsoft.Agents.BotBuilder /// An adapter that implements the Activity Protocol and can be hosted in different cloud environments both public and private. /// /// The IConnectorFactory to use. - /// /// The ILogger implementation this adapter should use. public abstract class ChannelServiceAdapterBase( IChannelServiceClientFactory channelServiceClientFactory, - ITurnState state = null, ILogger logger = null) : ChannelAdapter(logger) { /// @@ -46,8 +44,6 @@ public abstract class ChannelServiceAdapterBase( /// protected ILogger Logger { get; private set; } = logger ?? NullLogger.Instance; - private ITurnState State = state ?? new TurnState(); - /// public override async Task SendActivitiesAsync(ITurnContext turnContext, IActivity[] activities, CancellationToken cancellationToken) { @@ -79,7 +75,7 @@ public override async Task SendActivitiesAsync(ITurnContext } else if (activity.Type == ActivityTypes.InvokeResponse) { - turnContext.TurnState.Temp.SetValue(InvokeResponseKey, activity); + turnContext.StackState.Set(InvokeResponseKey, activity); } else if (activity.Type == ActivityTypes.Trace && activity.ChannelId != Channels.Emulator) { @@ -89,12 +85,12 @@ public override async Task SendActivitiesAsync(ITurnContext { if (!string.IsNullOrWhiteSpace(activity.ReplyToId)) { - var connectorClient = turnContext.TurnState.Temp.GetValue(); + var connectorClient = turnContext.Services.Get(); response = await connectorClient.Conversations.ReplyToActivityAsync(activity, cancellationToken).ConfigureAwait(false); } else { - var connectorClient = turnContext.TurnState.Temp.GetValue(); + var connectorClient = turnContext.Services.Get(); response = await connectorClient.Conversations.SendToConversationAsync(activity, cancellationToken).ConfigureAwait(false); } } @@ -115,7 +111,7 @@ public override async Task UpdateActivityAsync(ITurnContext tu Logger.LogInformation($"UpdateActivityAsync ActivityId: {activity.Id}"); - var connectorClient = turnContext.TurnState.Temp.GetValue(); + var connectorClient = turnContext.Services.Get(); return await connectorClient.Conversations.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); } @@ -127,7 +123,7 @@ public override async Task DeleteActivityAsync(ITurnContext turnContext, Convers Logger.LogInformation($"DeleteActivityAsync Conversation Id: {reference.Conversation.Id}, ActivityId: {reference.ActivityId}"); - var connectorClient = turnContext.TurnState.Temp.GetValue(); + var connectorClient = turnContext.Services.Get(); await connectorClient.Conversations.DeleteActivityAsync(reference.Conversation.Id, reference.ActivityId, cancellationToken).ConfigureAwait(false); } @@ -331,11 +327,14 @@ private static Activity CreateCreateActivity(ConversationResourceResponse create private TurnContext CreateTurnContext(IActivity activity, ClaimsIdentity claimsIdentity, string oauthScope, IConnectorClient connectorClient, IUserTokenClient userTokenClient, BotCallbackHandler callback) { - var turnContext = new TurnContext(this, activity, State); - //turnContext.TurnState.Temp.SetValue(BotIdentityKey, claimsIdentity); - turnContext.TurnState.Temp.SetValue(connectorClient); - turnContext.TurnState.Temp.SetValue(userTokenClient); - turnContext.TurnState.Temp.SetValue(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set + var turnContext = new TurnContext(this, activity); + + turnContext.StackState.Set(BotIdentityKey, claimsIdentity); + turnContext.StackState.Set(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set + + turnContext.Services.Set(connectorClient); + turnContext.Services.Set(userTokenClient); + turnContext.Services.Set(ChannelServiceFactory); return turnContext; } @@ -358,7 +357,7 @@ private static InvokeResponse ProcessTurnResults(TurnContext turnContext) // Handle Invoke scenarios where the Bot will return a specific body and return code. if (turnContext.Activity.Type == ActivityTypes.Invoke) { - var activityInvokeResponse = turnContext.TurnState.Temp.GetValue(InvokeResponseKey); + var activityInvokeResponse = turnContext.StackState.Get(InvokeResponseKey); if (activityInvokeResponse == null) { return new InvokeResponse { Status = (int)HttpStatusCode.NotImplemented }; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs index 4c907a71..0077ca23 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ActivityHandler.cs @@ -95,7 +95,7 @@ public virtual async Task OnTurnAsync(ITurnContext turnContext, CancellationToke var invokeResponse = await OnInvokeActivityAsync(new TypedTurnContext(turnContext), cancellationToken).ConfigureAwait(false); // If OnInvokeActivityAsync has already sent an InvokeResponse, do not send another one. - if (invokeResponse != null && turnContext.TurnState.Temp.GetValue(ChannelAdapter.InvokeResponseKey) == null) + if (invokeResponse != null && turnContext.StackState.Get(ChannelAdapter.InvokeResponseKey) == null) { await turnContext.SendActivityAsync(new Activity { Value = invokeResponse, Type = ActivityTypes.InvokeResponse }, cancellationToken).ConfigureAwait(false); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs index 6beb3f4a..dcde3937 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs @@ -78,7 +78,7 @@ public async Task OnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string co var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); + turnContext.StackState.Set(SkillConversationReferenceKey, skillConversationReference); await turnContext.DeleteActivityAsync(activityId, cancellationToken).ConfigureAwait(false); }); @@ -92,7 +92,7 @@ public async Task OnUpdateActivityAsync(ClaimsIdentity claimsI ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); + turnContext.StackState.Set(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = activityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; @@ -111,7 +111,7 @@ public async Task OnGetMemberAsync(ClaimsIdentity claimsIdentity var callback = new BotCallbackHandler(async (turnContext, ct) => { - var client = turnContext.TurnState.Temp.GetValue(); + var client = turnContext.Services.Get(); var conversationId = turnContext.Activity.Conversation.Id; member = await client.Conversations.GetConversationMemberAsync(userId, conversationId, cancellationToken).ConfigureAwait(false); }); @@ -148,7 +148,7 @@ public async Task OnGetConversationMemberAsync(ClaimsIdentity cl var callback = new BotCallbackHandler(async (turnContext, ct) => { - var client = turnContext.TurnState.Temp.GetValue(); + var client = turnContext.Services.Get(); var conversationId = turnContext.Activity.Conversation.Id; member = await client.Conversations.GetConversationMemberAsync(userId, conversationId, cancellationToken).ConfigureAwait(false); }); @@ -219,7 +219,7 @@ private async Task ProcessActivityAsync(ClaimsIdentity claimsI var callback = new BotCallbackHandler(async (turnContext, ct) => { - turnContext.TurnState.Temp.SetValue(SkillConversationReferenceKey, skillConversationReference); + turnContext.StackState.Set(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index 840f0222..1ed430a7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -80,12 +80,6 @@ public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, Cance await FinishTypingTaskAsync(turnContext).ConfigureAwait(false); } - private static bool IsSkillBot(ITurnContext turnContext) - { - return turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity - && BotClaims.IsBotClaim(claimIdentity.Claims); - } - private static async Task SendTypingAsync(ITurnContext turnContext, TimeSpan delay, TimeSpan period, CancellationToken cancellationToken) { try @@ -178,12 +172,9 @@ private async Task FinishTypingTaskAsync(ITurnContext turnContext) /// The context object for this turn. private async Task ProcessTypingAsync(ITurnContext turnContext) { - if (!IsSkillBot(turnContext) && turnContext.Activity.Type == ActivityTypes.Message) - { - // Override the typing background task. - await FinishTypingTaskAsync(turnContext).ConfigureAwait(false); - StartTypingTask(turnContext); - } + // Override the typing background task. + await FinishTypingTaskAsync(turnContext).ConfigureAwait(false); + StartTypingTask(turnContext); } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs index 6b603f2d..930eb85c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System.Runtime.CompilerServices; using System.Threading; @@ -34,11 +33,8 @@ internal class TypedTurnContext(ITurnContext innerTurnContext) : ITurnContext /// The bot adapter that created this context object. public IChannelAdapter Adapter => _innerTurnContext.Adapter; - /// - /// Gets the collection of values cached with the context object for the lifetime of the turn. - /// - /// The collection of services registered on this context object. - public ITurnState TurnState => _innerTurnContext.TurnState; + public TurnContextStateCollection Services => _innerTurnContext.Services; + public TurnContextStateCollection StackState => _innerTurnContext.StackState; /// /// Gets the activity for this turn of the bot. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs index f378f8cd..52a594db 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs @@ -109,11 +109,8 @@ public interface ITurnContext /// The bot adapter that created this context object. IChannelAdapter Adapter { get; } - /// - /// Gets the collection of values cached with the context object for the lifetime of the turn. - /// - /// The collection of services registered on this context object. - ITurnState TurnState { get; } + TurnContextStateCollection Services { get; } + TurnContextStateCollection StackState { get; } /// /// Gets the activity for this turn of the bot. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs index 86384660..07eebd2a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs @@ -118,7 +118,7 @@ public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken c public static IUserTokenClient GetTokenClient(ITurnContext turnContext) { ArgumentNullException.ThrowIfNull(turnContext); - var userTokenClient = turnContext.TurnState.Temp.GetValue(); + var userTokenClient = turnContext.Services.Get(); if (userTokenClient != null) { return userTokenClient; @@ -218,12 +218,6 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt }); } - // Add the login timeout specified in OAuthPromptSettings to TurnState so it can be referenced if polling is needed - if (!turnContext.TurnState.Temp.HasValue(OAuthTurnStateConstants.OAuthLoginTimeoutKey) && Timeout.HasValue) - { - turnContext.TurnState.Temp.SetValue(OAuthTurnStateConstants.OAuthLoginTimeoutKey, TimeSpan.FromMilliseconds(Timeout.Value)); - } - // Set input hint if (string.IsNullOrEmpty(prompt.InputHint)) { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs index 982fdc13..4d8c8d85 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs @@ -10,16 +10,6 @@ namespace Microsoft.Agents.BotBuilder /// public static class OAuthTurnStateConstants { - /// - /// TurnState key for the OAuth login timeout. - /// - public const string OAuthLoginTimeoutKey = "loginTimeout"; - - /// - /// Name of the token polling settings key. - /// - public const string TokenPollingSettingsKey = "tokenPollingSettings"; - /// /// Default amount of time an OAuthCard will remain active (clickable and actively waiting for a token). /// After this time: diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs index b972533f..54bf444a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs @@ -75,7 +75,7 @@ public bool HasValue(string name) } var cachedState = GetCachedState(); - return ObjectPath.HasValue(cachedState.State, name); + return cachedState.State.ContainsKey(name); } /// @@ -179,7 +179,7 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false var storageKey = GetStorageKey(turnContext); - if (ShouldLoad(storageKey, force)) + if (ShouldLoad(turnContext, storageKey, force)) { var items = await _storage.ReadAsync([storageKey], cancellationToken).ConfigureAwait(false); items.TryGetValue(storageKey, out object val); @@ -199,21 +199,17 @@ public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false } else { - // This should never happen throw new InvalidOperationException("Data is not in the correct format for BotState."); } + + turnContext.StackState.Set(Name, _cachedBotState); } } - private bool ShouldLoad(string storageKey, bool force) + private bool ShouldLoad(ITurnContext turnContext, string storageKey, bool force) { - var cachedState = GetCachedState(); - if (cachedState != null && cachedState.Key != storageKey) - { - throw new InvalidOperationException($"BotState '{GetType().Name}' is being used by multiple conversations. Verify \"AddTransient\" DI registration."); - } - - return force || cachedState == null || cachedState.State == null; + _cachedBotState = turnContext.StackState.Get(Name); + return force || _cachedBotState == null || _cachedBotState.State == null; } /// @@ -322,8 +318,37 @@ protected T GetPropertyValue(string propertyName) { ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); + if (!IsLoaded()) + { + throw new InvalidOperationException($"{Name} is not loaded"); + } + var cachedState = GetCachedState(); - return ObjectPath.GetPathValue(cachedState.State, propertyName, true); + if (cachedState.State.TryGetValue(propertyName, out object result)) + { + if (result is T t) + { + return t; + } + + if (result == null) + { + return default(T); + } + + // If types are not used by storage serialization try to convert the object to the type expected + // using the serializer. + var converted = ProtocolJsonSerializer.ToObject(result); + cachedState.State[propertyName] = converted; + return converted; + } + + if (typeof(T).IsValueType) + { + throw new KeyNotFoundException(propertyName); + } + + return default(T); } /// @@ -350,8 +375,7 @@ protected void SetPropertyValue(string propertyName, object value) ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); var cachedState = GetCachedState(); - //cachedState.State[propertyName] = value; - ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); + cachedState.State[propertyName] = value; } /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs index 03d4892d..1bd27305 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; @@ -39,11 +38,12 @@ public class TurnContext : ITurnContext, IDisposable /// or /// is null. /// For use by bot adapter implementations only. - public TurnContext(IChannelAdapter adapter, IActivity activity, ITurnState state = null) + public TurnContext(IChannelAdapter adapter, IActivity activity) { Adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - TurnState = state ?? (ITurnState) new TurnState(); + StackState = new TurnContextStateCollection(); + Services = new TurnContextStateCollection(); } /// @@ -63,7 +63,8 @@ public TurnContext(ITurnContext turnContext, IActivity activity) // all properties should be copied over except for activity. Adapter = turnContext.Adapter; - TurnState = turnContext.TurnState; + StackState = new TurnContextStateCollection(); + Services = new TurnContextStateCollection(); Responded = turnContext.Responded; if (turnContext is TurnContext tc) @@ -83,11 +84,13 @@ public TurnContext(ITurnContext turnContext, IActivity activity) /// The bot adapter that created this context object. public IChannelAdapter Adapter { get; } + public TurnContextStateCollection StackState { get; } + /// /// Gets the services registered on this context object. /// /// The services registered on this context object. - public ITurnState TurnState { get; } + public TurnContextStateCollection Services { get; } /// /// Gets the activity associated with this turn; or null when processing @@ -274,7 +277,7 @@ async Task SendActivitiesThroughAdapter() // is not being sent through the adapter, where it would be added to TurnState. if (activity.Type == ActivityTypes.InvokeResponse) { - TurnState.Temp.SetValue(ChannelAdapter.InvokeResponseKey, activity); + StackState.Add(ChannelAdapter.InvokeResponseKey, activity); } responses[index] = new ResourceResponse(); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContextStateCollection.cs similarity index 72% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContextStateCollection.cs index c32430e1..2a06b1a3 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TurnContextStateCollection.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContextStateCollection.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.BotBuilder.Compat +namespace Microsoft.Agents.BotBuilder { /// /// Values persisted for the lifetime of the turn as part of the . @@ -34,7 +34,6 @@ public TurnContextStateCollection() /// The object; or null if no service is registered by the key, or /// the retrieved object does not match the object type. public T Get(string key) - where T : class { if (_disposed) { @@ -55,7 +54,7 @@ public T Get(string key) } // return null if either the key or type don't match - return null; + return default; } /// @@ -70,48 +69,6 @@ public T Get() return Get(typeof(T).FullName); } - /// - /// Adds a value to the turn's context. - /// - /// The type of the object. - /// The name of the object. - /// The value to add. - /// or is null. - public void Add(string key, T value) - where T : class - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(Add)); - } - - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - // note this can throw if the key is already present - base.Add(key, value); - } - - /// - /// Adds a value to the turn's context. - /// - /// The type of the object. - /// The object to add. - /// is null. - /// The default service key is the of the object type. - public void Add(T value) - where T : class - { - Add(typeof(T).FullName, value); - } - /// /// Set a value to the turn's context. /// @@ -120,7 +77,6 @@ public void Add(T value) /// The value to add. /// or is null. public void Set(string key, T value) - where T : class { if (_disposed) { @@ -147,6 +103,11 @@ public void Set(T value) Set(typeof(T).FullName, value); } + public bool Has(string key) + { + return ContainsKey(key); + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/libraries/Core/Microsoft.Agents.State/AssemblyInfo.cs b/src/libraries/Core/Microsoft.Agents.State/AssemblyInfo.cs deleted file mode 100644 index 0ea8ea6e..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Runtime.CompilerServices; - -// Allows us to access some internal methods from the Memory.Tests unit tests so we don't have to use reflection and we get compile checks. -[assembly: InternalsVisibleTo("Microsoft.Agents.State.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs b/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs deleted file mode 100644 index faab5916..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/AutoSaveStateMiddleware.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - /// - /// Middleware to automatically persist state before the end of each turn. - /// - /// - /// This calls - /// on each state object it manages. - /// - public class AutoSaveStateMiddleware : IMiddleware - { - private readonly bool _autoLoad; - - /// - /// Initializes a new instance of the class. - /// - /// initial list of objects to manage. - public AutoSaveStateMiddleware(params BotState[] botStates) - { - _autoLoad = false; - BotStateSet = new BotStateSet(botStates); - } - - /// - /// Allows for optionally auto-loading BotState at turn start. - /// - /// - /// - public AutoSaveStateMiddleware(bool autoLoad, params BotState[] botStates) - { - _autoLoad = autoLoad; - BotStateSet = new BotStateSet(botStates); - } - - /// - /// Initializes a new instance of the class with - /// a list of state management objects managed by this object. - /// - /// The state management objects managed by this object. - public AutoSaveStateMiddleware(BotStateSet botStateSet) - { - BotStateSet = botStateSet; - } - - /// - /// Gets or sets the list of state management objects managed by this object. - /// - /// The state management objects managed by this object. - public BotStateSet BotStateSet { get; set; } - - /// - /// Adds a state management object to the list of states to manage. - /// - /// The bot state to add. - /// The updated object. - public AutoSaveStateMiddleware Add(BotState botState) - { - ArgumentNullException.ThrowIfNull(botState); - - BotStateSet.Add(botState); - return this; - } - - /// - /// Before the turn ends, calls - /// on each state object. - /// - /// The context object for this turn. - /// The delegate to call to continue the bot middleware pipeline. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// This middleware persists state after the bot logic completes and before the turn ends. - public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) - { - // before turn - if (_autoLoad) - { - await BotStateSet.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); - } - - await next(cancellationToken).ConfigureAwait(false); - - // after turn - await BotStateSet.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/BotState.cs b/src/libraries/Core/Microsoft.Agents.State/BotState.cs deleted file mode 100644 index ae3b6286..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/BotState.cs +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Storage; -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - /// - /// Defines a state management object and automates the reading and writing of associated state - /// properties to a storage layer. - /// - /// - /// Each state management object defines a scope for a storage layer. - /// - /// State properties are created within a state management scope, and the Agents SDK - /// defines these scopes: - /// , , and . - /// - /// You can define additional scopes for your bot. - /// - /// - public abstract class BotState : IPropertyManager, IBotState - { - private readonly IStorage _storage; - private CachedBotState _cachedBotState; - - /// - /// Initializes a new instance of the class. - /// - /// The storage layer this state management object will use to store - /// and retrieve state. - /// The key for the state cache for this . - /// This constructor creates a state management object and associated scope. - /// The object uses to persist state property values. - /// The object uses the to cache state within the context for each turn. - /// - /// or - /// is null. - /// - public BotState(IStorage storage, string stateName) - { - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - Name = stateName ?? throw new ArgumentNullException(nameof(stateName)); - } - - public string Name { get; private set; } - - /// - /// Creates a named state property within the scope of a and returns - /// an accessor for the property. - /// - /// The value type of the property. - /// The name of the property. - /// An accessor for the property. - /// is null. - [Obsolete("Use BotState.GetValue and BotState.SetValue")] - public IStatePropertyAccessor CreateProperty(string name) - { - ArgumentException.ThrowIfNullOrWhiteSpace(name); - return new BotStatePropertyAccessor(this, name); - } - - public bool HasValue(string name) - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - var cachedState = GetCachedState(); - return ObjectPath.HasValue(cachedState.State, name); - } - - /// - /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// A representing the asynchronous operation. - public void DeleteValue(string name) - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - DeletePropertyValue(name); - } - - /// - /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// Defines the default value. - /// Invoked when no value been set for the requested state property. - /// If defaultValueFactory is defined as null in that case, the method returns null and - public T GetValue(string name, Func defaultValueFactory = null) - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - T result = default; - - try - { - // if T is a value type, lookup up will throw key not found if not found, but as perf - // optimization it will return null if not found for types which are not value types (string and object). - result = GetPropertyValue(name); - - if (result == null && defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - SetValue(name, result); - } - } - catch (KeyNotFoundException) - { - if (defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - SetValue(name, result); - } - } - - return result; - } - - /// - /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// value. - /// A representing the asynchronous operation. - public void SetValue(string name, T value) - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - SetPropertyValue(name, value); - } - - /// - /// True if state has been loaded. - /// - /// - public bool IsLoaded() - { - return _cachedBotState != null; - } - - /// - /// Populates the state cache for this from the storage layer. - /// - /// - /// LoadAsync loads State for the specified turn. - /// - /// The context object for this turn. - /// Optional, true to overwrite any existing state cache; - /// or false to load state from storage only if the cache doesn't already exist. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. - public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(turnContext); - - var storageKey = GetStorageKey(turnContext); - - if (ShouldLoad(storageKey, force)) - { - var items = await _storage.ReadAsync([storageKey], cancellationToken).ConfigureAwait(false); - items.TryGetValue(storageKey, out object val); - - if (val is IDictionary asDictionary) - { - _cachedBotState = new CachedBotState(storageKey, asDictionary); - } - else if (val is JsonObject || val is JsonElement) - { - _cachedBotState = new CachedBotState(storageKey, ProtocolJsonSerializer.ToObject>(val)); - } - else if (val == null) - { - // This is the case where the dictionary did not exist in the store. - _cachedBotState = new CachedBotState(storageKey); - } - else - { - // This should never happen - throw new InvalidOperationException("Data is not in the correct format for BotState."); - } - } - } - - private bool ShouldLoad(string storageKey, bool force) - { - var cachedState = GetCachedState(); - if (cachedState != null && cachedState.Key != storageKey) - { - throw new InvalidOperationException($"BotState '{GetType().Name}' is being used by multiple conversations. Verify \"AddTransient\" DI registration."); - } - - return force || cachedState == null || cachedState.State == null; - } - - /// - /// Writes the state cache for this to the storage layer. - /// - /// The context object for this turn. - /// Optional, true to save the state cache to storage; - /// or false to save state to storage only if a property in the cache has changed. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. - public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(turnContext); - - var cachedState = GetCachedState(); - if (cachedState != null && (force || cachedState.IsChanged())) - { - var key = GetStorageKey(turnContext); - var changes = new Dictionary - { - { key, cachedState.State }, - }; - await _storage.WriteAsync(changes, cancellationToken).ConfigureAwait(false); - cachedState.Hash = CachedBotState.ComputeHash(cachedState.State); - return; - } - } - - /// - /// Clears the state cache for this . - /// - /// A task that represents the work queued to execute. - /// This method clears the state cache in the turn context. Call - /// to persist this - /// change in the storage layer. - /// - public virtual void ClearState() - { - if (!IsLoaded()) - { - throw new InvalidOperationException($"{Name} is not loaded"); - } - - // Explicitly setting the hash will mean IsChanged is always true. And that will force a Save. - GetCachedState().Clear(); - } - - /// - /// Deletes any state in storage and the cache for this . - /// - /// The context object for this turn. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. - public virtual async Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) - { - if (IsLoaded()) - { - ClearState(); - } - - var storageKey = GetStorageKey(turnContext); - await _storage.DeleteAsync(new[] { storageKey }, cancellationToken).ConfigureAwait(false); - } - - /// - /// Gets a copy of the raw cached data for this from the turn context. - /// - /// A JSON representation of the cached state. - internal JsonElement Get() - { - var cachedState = GetCachedState(); - return JsonSerializer.SerializeToElement(cachedState.State, ProtocolJsonSerializer.SerializationOptions); - } - - /// - /// Gets the cached bot state instance that wraps the raw cached data for this - /// from the turn context. - /// - /// The cached bot state instance. - internal CachedBotState GetCachedState() - { - return _cachedBotState; - } - - /// - /// When overridden in a derived class, gets the key to use when reading and writing state to and from storage. - /// - /// The context object for this turn. - /// The storage key. - protected abstract string GetStorageKey(ITurnContext turnContext); - - /// - /// Gets the value of a property from the state cache for this . - /// - /// The value type of the property. - /// The name of the property. - /// A task that represents the work queued to execute. - /// If the task is successful, the result contains the property value, otherwise it will be default(T). -#pragma warning disable CA1801 // Review unused parameters (we can't change this without breaking binary compat) - protected T GetPropertyValue(string propertyName) -#pragma warning restore CA1801 // Review unused parameters - { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - - var cachedState = GetCachedState(); - return ObjectPath.GetPathValue(cachedState.State, propertyName, true); - } - - /// - /// Deletes a property from the state cache for this . - /// - /// The name of the property. - /// A task that represents the work queued to execute. - protected void DeletePropertyValue(string propertyName) - { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - - var cachedState = GetCachedState(); - cachedState.State.Remove(propertyName); - } - - /// - /// Sets the value of a property in the state cache for this . - /// - /// The name of the property to set. - /// The value to set on the property. - /// A task that represents the work queued to execute. - protected void SetPropertyValue(string propertyName, object value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(propertyName); - - var cachedState = GetCachedState(); - //cachedState.State[propertyName] = value; - ObjectPath.SetPathValue(cachedState.State, propertyName, value, false); - } - - /// - /// Internal cached bot state. - /// - internal class CachedBotState - { - /// - /// Initializes a new instance of the class. - /// - /// Unique state key. Typically the storage key. - /// Initial state for the . - public CachedBotState(string key, IDictionary state = null) - { - State = state ?? new Dictionary(); - Hash = ComputeHash(State); - Key = key; - } - - /// - /// Gets or sets the state as a dictionary of key value pairs. - /// - /// - /// The state as a dictionary of key value pairs. - /// -#pragma warning disable CA2227 // Collection properties should be read only (we can't change this without breaking binary compat) - public IDictionary State { get; set; } -#pragma warning restore CA2227 // Collection properties should be read only - - internal string Hash { get; set; } - - internal string Key { get; set; } - - internal static string ComputeHash(object obj) - { - return ProtocolJsonSerializer.ToJson(obj); - } - - internal bool IsChanged() - { - return Hash != ComputeHash(State); - } - - internal void Clear() - { - State = new Dictionary(); - Hash = string.Empty; - } - } - - #region Obsolete BotStatePropertyAccessor - /// - /// Implements an for a property container. - /// Note the semantics of this accessor are intended to be lazy, this means the Get, Set and Delete - /// methods will first call LoadAsync. This will be a no-op if the data is already loaded. - /// The implication is you can just use this accessor in the application code directly without first calling LoadAsync - /// this approach works with the AutoSaveStateMiddleware which will save as needed at the end of a turn. - /// - /// type of value the propertyAccessor accesses. - private class BotStatePropertyAccessor : IStatePropertyAccessor - { - private BotState _botState; - - public BotStatePropertyAccessor(BotState botState, string name) - { - _botState = botState; - Name = name; - } - - /// - /// Gets name of the property. - /// - /// - /// name of the property. - /// - public string Name { get; private set; } - - /// - /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// The turn context. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - _botState.DeleteValue(Name); - } - - /// - /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// The context object for this turn. - /// Defines the default value. - /// Invoked when no value been set for the requested state property. - /// If defaultValueFactory is defined as null in that case, the method returns null and - /// SetAsync is not called. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) - { - T result = default(T); - - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - - try - { - // if T is a value type, lookup up will throw key not found if not found, but as perf - // optimization it will return null if not found for types which are not value types (string and object). - result = _botState.GetValue(Name, defaultValueFactory); - - if (result == null && defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - catch (KeyNotFoundException) - { - if (defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - - return result; - } - - /// - /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// turn context. - /// value. - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken) - { - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - _botState.SetValue(Name, value); - } - } - #endregion - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs b/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs deleted file mode 100644 index e1e678c3..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/BotStateSet.cs +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Storage; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace Microsoft.Agents.State -{ - /// - /// Manages a collection of botState and provides ability to load and save in parallel. - /// - public class BotStateSet : ITurnState - { - private readonly Dictionary _scopes = []; - - /// - /// Initializes a new instance of the class. - /// - /// initial list of objects to manage. - public BotStateSet(params IBotState[] botStates) - { - foreach (var botState in botStates) - { - _scopes.Add(botState.Name, botState); - } - } - - /// - /// Creates BotStateSet with default ConversationState and UserState - /// - /// - /// Additional list of BotState objects to manage. - public BotStateSet(IStorage storage, params IBotState[] botStates) - { - _scopes.Add(ConversationState.ScopeName, new ConversationState(storage)); - _scopes.Add(UserState.ScopeName, new UserState(storage)); - _scopes.Add(TempState.ScopeName, new TempState()); - - foreach (var botState in botStates) - { - _scopes[botState.Name] = botState; - } - } - - public ConversationState Conversation => GetScope(); - public UserState User => GetScope(); - public PrivateConversationState Private => GetScope(); - public TempState Temp => GetScope(); - - public bool HasValue(string path) - { - var (scope, property) = GetScopeAndPath(path); - return GetScope(scope).HasValue(property); - } - - public T GetValue(string name, Func defaultValueFactory = null) - { - var (scope, property) = GetScopeAndPath(name); - return GetScope(scope).GetValue(property, defaultValueFactory); - } - - public void SetValue(string path, object value) - { - var (scope, property) = GetScopeAndPath(path); - GetScope(scope).SetValue(property, value); - } - - public void DeleteValue(string path) - { - var (scope, property) = GetScopeAndPath(path); - GetScope(scope).DeleteValue(property); - } - - public IBotState GetScope(string scope) - { - if (!_scopes.TryGetValue(scope, out IBotState value)) - { - throw new ArgumentException($"Scope '{scope}' not found"); - } - return value; - } - - public T GetScope() - { - foreach (var scope in _scopes) - { - if (scope.Value is T botState) - { - return botState; - } - } - - throw new ArgumentException($"Scope '{nameof(T)}' not found"); - } - - public BotStateSet Add(IBotState botState) - { - ArgumentNullException.ThrowIfNull(botState); - _scopes.Add(botState.Name, botState); - return this; - } - - /// - /// Load all BotState records in parallel. - /// - /// turn context. - /// should data be forced into cache. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - public async Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - var tasks = _scopes.Select(bs => bs.Value.LoadAsync(turnContext, force, cancellationToken)).ToList(); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - public void ClearState(string scope) - { - GetScope(scope).ClearState(); - } - - /// - /// Save All BotState changes in parallel. - /// - /// turn context. - /// should data be forced to save even if no change were detected. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - public async Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - var tasks = _scopes.Select(kv => kv.Value.SaveChangesAsync(turnContext, force, cancellationToken)).ToList(); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private static (string, string) GetScopeAndPath(string name) - { - var scopeEnd = name.IndexOf('.'); - if (scopeEnd == -1) - { - throw new ArgumentException("Path must include the state scope name"); - } - return (name.Substring(0, scopeEnd), name.Substring(scopeEnd + 1)); - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs deleted file mode 100644 index 8f6a69fb..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/ConversationState.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Storage; -using System; - -namespace Microsoft.Agents.State -{ - /// - /// Defines a state keyed to a conversation. - /// - /// - /// Conversation state is available in any turn in a specific conversation, regardless of user, - /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. - /// - /// The storage layer to use. - public class ConversationState(IStorage storage) : BotState(storage, ScopeName) - { - public static readonly string ScopeName = "conversation"; - - /// - /// Gets the key to use when reading and writing state to and from storage. - /// - /// The context object for this turn. - /// The storage key. - /// - /// Conversation state includes the channel ID and conversation ID as part of its storage key. - /// - protected override string GetStorageKey(ITurnContext turnContext) - { - var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); - var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); - return $"{channelId}/conversations/{conversationId}"; - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs b/src/libraries/Core/Microsoft.Agents.State/IBotState.cs deleted file mode 100644 index 95c38537..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IBotState.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - public interface IBotState - { - string Name { get; } - - void ClearState(); - Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); - void DeleteValue(string name); - T GetValue(string name, Func defaultValueFactory = null); - bool IsLoaded(); - Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - void SetValue(string name, T value); - bool HasValue(string name); - } -} \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs b/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs deleted file mode 100644 index ba62e082..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IPropertyManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.Agents.State -{ - /// - /// IPropertyManager defines implementation of a source of named properties. - /// - public interface IPropertyManager - { - /// - /// Creates a managed state property accessor for a property. - /// - /// The property value type. - /// The name of the property accessor. - /// A state property accessor for the property. - [Obsolete("Use BotState.GetPropertyAsync")] - IStatePropertyAccessor CreateProperty(string name); - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs deleted file mode 100644 index a420108e..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyAccessor.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - /// - /// Interface which defines methods for how you can get data from a property source, - /// such as . - /// - /// type of the property. - public interface IStatePropertyAccessor : IStatePropertyInfo - { - /// - /// Gets the property value from the source. - /// - /// Turn Context. - /// Function which defines the property value to be returned if no value has been set. - /// The cancellation token. - /// A representing the result of the asynchronous operation. - Task GetAsync(ITurnContext turnContext, Func defaultValueFactory = null, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Delete the property from the source. - /// - /// Turn Context. - /// The cancellation token. - /// A representing the asynchronous operation. - Task DeleteAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Set the property value on the source. - /// - /// Turn Context. - /// The value to set. - /// The cancellation token. - /// A representing the asynchronous operation. - Task SetAsync(ITurnContext turnContext, T value, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs b/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs deleted file mode 100644 index 50ac6103..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/IStatePropertyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.Agents.State -{ - /// - /// Metadata about a property, including policy info. - /// - public interface IStatePropertyInfo - { - /// - /// Gets the name of the property. - /// - /// - /// The name of the property. - /// - string Name { get; } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs b/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs deleted file mode 100644 index c7a924d4..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/ITurnState.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - public interface ITurnState - { - ConversationState Conversation { get; } - PrivateConversationState Private { get; } - TempState Temp { get; } - UserState User { get; } - - IBotState GetScope(string scope); - T GetScope(); - - T GetValue(string path, Func defaultValueFactory = null); - void SetValue(string path, object value); - void DeleteValue(string path); - bool HasValue(string path); - - void ClearState(string scope); - Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/libraries/Core/Microsoft.Agents.State/Microsoft.Agents.State.csproj b/src/libraries/Core/Microsoft.Agents.State/Microsoft.Agents.State.csproj deleted file mode 100644 index 12a98236..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/Microsoft.Agents.State.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - latest - CplState - true - README.md - - - - - Microsoft.Agents.State - Library for building agents using Microsoft Agents SDK - Library for building agents using Microsoft Agents SDK - - - - - - - - - - - diff --git a/src/libraries/Core/Microsoft.Agents.State/ObjectMerge.cs b/src/libraries/Core/Microsoft.Agents.State/ObjectMerge.cs deleted file mode 100644 index e4c5811b..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/ObjectMerge.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Buffers; -using System.Diagnostics; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; - -namespace Microsoft.Agents.State -{ - internal class ObjectMerge - { - public static JsonNode Merge(JsonNode originalNode, JsonNode newNode) - { - JsonElement originalElement = JsonSerializer.SerializeToElement(originalNode); - JsonElement newElement = JsonSerializer.SerializeToElement(newNode); - - if (originalElement.ValueKind != JsonValueKind.Array && originalElement.ValueKind != JsonValueKind.Object) - { - throw new InvalidOperationException($"The original JSON to merge new content into must be a container type. Instead it is {originalElement.ValueKind}."); - } - - var outputBuffer = new ArrayBufferWriter(); - using (var jsonWriter = new Utf8JsonWriter(outputBuffer, new JsonWriterOptions { Indented = true })) - { - if (originalElement.ValueKind != newElement.ValueKind) - { - return originalNode; - } - - if (originalElement.ValueKind == JsonValueKind.Array) - { - MergeArrays(jsonWriter, originalElement, newElement); - } - else - { - MergeObjects(jsonWriter, originalElement, newElement); - } - } - - return JsonSerializer.Deserialize(Encoding.UTF8.GetString(outputBuffer.WrittenSpan)); - } - - private static void MergeObjects(Utf8JsonWriter jsonWriter, JsonElement root1, JsonElement root2) - { - Debug.Assert(root1.ValueKind == JsonValueKind.Object); - Debug.Assert(root2.ValueKind == JsonValueKind.Object); - - jsonWriter.WriteStartObject(); - - // Write all the properties of the first document. - // If a property exists in both documents, either: - // * Merge them, if the value kinds match (e.g. both are objects or arrays), - // * Completely override the value of the first with the one from the second, if the value kind mismatches (e.g. one is object, while the other is an array or string), - // * Or favor the value of the first (regardless of what it may be), if the second one is null (i.e. don't override the first). - foreach (JsonProperty property in root1.EnumerateObject()) - { - string propertyName = property.Name; - - JsonValueKind newValueKind; - - if (root2.TryGetProperty(propertyName, out JsonElement newValue) && (newValueKind = newValue.ValueKind) != JsonValueKind.Null) - { - jsonWriter.WritePropertyName(propertyName); - - JsonElement originalValue = property.Value; - JsonValueKind originalValueKind = originalValue.ValueKind; - - if (newValueKind == JsonValueKind.Object && originalValueKind == JsonValueKind.Object) - { - MergeObjects(jsonWriter, originalValue, newValue); // Recursive call - } - else if (newValueKind == JsonValueKind.Array && originalValueKind == JsonValueKind.Array) - { - MergeArrays(jsonWriter, originalValue, newValue); - } - else - { - newValue.WriteTo(jsonWriter); - } - } - else - { - property.WriteTo(jsonWriter); - } - } - - // Write all the properties of the second document that are unique to it. - foreach (JsonProperty property in root2.EnumerateObject()) - { - if (!root1.TryGetProperty(property.Name, out _)) - { - property.WriteTo(jsonWriter); - } - } - - jsonWriter.WriteEndObject(); - } - - private static void MergeArrays(Utf8JsonWriter jsonWriter, JsonElement root1, JsonElement root2) - { - Debug.Assert(root1.ValueKind == JsonValueKind.Array); - Debug.Assert(root2.ValueKind == JsonValueKind.Array); - - jsonWriter.WriteStartArray(); - - // Write all the elements from both JSON arrays - foreach (JsonElement element in root1.EnumerateArray()) - { - element.WriteTo(jsonWriter); - } - foreach (JsonElement element in root2.EnumerateArray()) - { - element.WriteTo(jsonWriter); - } - - jsonWriter.WriteEndArray(); - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs b/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs deleted file mode 100644 index 775775cf..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/ObjectPath.cs +++ /dev/null @@ -1,820 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Serialization; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -namespace Microsoft.Agents.State -{ - /// - /// Helper methods for working with dynamic json objects. - /// - public static class ObjectPath - { - private static readonly JsonSerializerOptions _serializerOptions = new() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - IncludeFields = true, - }; - - /// - /// Does an object have a subpath. - /// - /// object. - /// path to evaluate. - /// true if the path is there. - public static bool HasValue(object obj, string path) - { - return TryGetPathValue(obj, path, out var value); - } - - /// - /// Get the value for a path relative to an object. - /// - /// type to return. - /// object to start with. - /// path to evaluate. - /// Set path value with result if true. - /// value or default(T). - public static T GetPathValue(object obj, string path, bool set = false) - { - if (TryGetPathValue(obj, path, out var value, set)) - { - return value; - } - - throw new KeyNotFoundException(path); - } - - /// - /// Get the value for a path relative to an object. - /// - /// type to return. - /// object to start with. - /// path to evaluate. - /// default value to use if any part of the path is missing. - /// Set path value with result if true. - /// value or default(T). - public static T GetPathValue(object obj, string path, T defaultValue, bool set = false) - { - if (TryGetPathValue(obj, path, out var value, set)) - { - return value; - } - - return defaultValue; - } - - /// - /// Get the value for a path relative to an object. - /// - /// type to return. - /// object to start with. - /// path to evaluate. - /// value for the path. - /// If the value was converted, set the path value with the converted value. - /// true if successful. - public static bool TryGetPathValue(object obj, string path, out T value, bool convertedSet = false) - { - value = default; - - if (obj == null) - { - return false; - } - - if (path == null) - { - return false; - } - - if (path.Length == 0) - { - value = MapValueTo(obj); - return true; - } - - if (!TryResolvePath(obj, path, out var segments)) - { - return false; - } - - if (!ResolveSegments(obj, segments, out var result)) - { - return false; - } - - // look to see if it's ExpressionProperty and bind it if it is - // NOTE: this bit of duck typing keeps us from adding dependency between adaptiveExpressions and Dialogs. - if (result.GetType().GetProperty("ExpressionText") != null) - { - var method = result.GetType().GetMethod("GetValue", new[] { typeof(object) }); - if (method != null) - { - result = method.Invoke(result, new[] { obj }); - } - } - - try - { - value = MapValueTo(result); - if (convertedSet && value.GetType() != result.GetType()) - { - SetPathValueInner(segments, obj, value, false); - } - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception) -#pragma warning restore CA1031 // Do not catch general exception types - { - return false; - } - - return true; - } - - /// - /// Given an object evaluate a path to set the value. - /// - /// object to start with. - /// path to evaluate. - /// value to store. - /// if true, sets the value as primitive JSON objects. - public static void SetPathValue(object obj, string path, object value, bool json = true) - { - if (!TryResolvePath(obj, path, out var segments)) - { - return; - } - - SetPathValueInner(segments, obj, value, json); - } - - private static void SetPathValueInner(List segments, object obj, object value, bool json = true) - { - dynamic current = obj; - for (var i = 0; i < segments.Count - 1; i++) - { - var segment = segments[i]; - dynamic next; - if (segment is int index) - { - next = current[index]; - if (next == null) - { - if (((ICollection)current).Count <= index) - { - // Expand array to index - for (var idx = ((ICollection)current).Count; idx <= index; ++idx) - { - ((JsonArray)current)[idx] = null; - } - - next = current[index]; - } - } - } - else - { - var strSegment = segment as string; - next = GetObjectProperty(current, strSegment); - if (next == null) - { - // Create object or array base on next segment - var nextSegment = segments[i + 1]; - if (nextSegment is string) - { - SetObjectSegment(current, strSegment, new JsonObject(), json); - next = GetObjectProperty(current, strSegment); - } - else - { - SetObjectSegment(current, strSegment, new JsonArray(), json); - next = GetObjectProperty(current, strSegment); - } - } - } - - current = next; - } - - var lastSegment = segments.Last(); - SetObjectSegment(current, lastSegment, value, json); - } - - /// - /// Remove path from object. - /// - /// Object to change. - /// Path to remove. - public static void RemovePathValue(object obj, string path) - { - if (!TryResolvePath(obj, path, out var segments)) - { - return; - } - - dynamic current = obj; - for (var i = 0; i < segments.Count - 1; i++) - { - var segment = segments[i]; - if (!ResolveSegment(ref current, segment)) - { - return; - } - } - - if (current != null) - { - var lastSegment = segments.Last(); - if (lastSegment is string property) - { - // ConcurrentDictionary doesn't implement Remove, but it does implement IDictionary - if (current is IDictionary dict) - { - dict.Remove(property); - } - else - { - current.Remove(property); - } - } - else - { - current[(int)lastSegment] = null; - } - } - } - - /// - /// Apply an action to all properties in an object. - /// - /// Object to map against. - /// Action to take. - public static void ForEachProperty(object obj, Action action) - { - if (obj is IDictionary dict) - { - foreach (var entry in dict) - { - action(entry.Key, entry.Value); - } - } - else if (obj is JsonObject jobj) - { - foreach (var property in jobj) - { - action(property.Key, property.Value); - } - } - - /* For tracking purposes, only use pure dictionary/jobject. - else if (!(obj.GetType().IsPrimitive || obj.GetType().IsArray() || obj is string || obj is DateTime || obj is DateTimeOffset || obj is JValue || obj is JArray)) - { - foreach (var property in obj.GetType().GetProperties()) - { - // Check for indexer - if (property.GetIndexParameters().Length == 0) - { - action(property.Name, property.GetValue(obj)); - } - } - } - */ - } - - /// - /// Get all properties in an object. - /// - /// Object to enumerate property names. - /// enumeration of property names on the object if it is not a value type. - public static IEnumerable GetProperties(object obj) - { - if (obj == null) - { - } - else if (obj is IDictionary dict) - { - foreach (var entry in dict) - { - yield return entry.Key; - } - } - else if (obj is JsonObject jobj) - { - foreach (var property in jobj) - { - yield return property.Key; - } - } - else - { - foreach (var property in obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name)) - { - yield return property; - } - } - } - - /// - /// Detects if property exists on object. - /// - /// object. - /// name of the property. - /// true if found. - public static bool ContainsProperty(object obj, string name) - { - if (obj == null) - { - return false; - } - - if (obj is IDictionary dict) - { - return dict.ContainsKey(name); - } - - if (obj is JsonObject jobj) - { - return jobj.ContainsKey(name); - } - - return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Any(property => property.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); - } - - /// - /// Clone an object. - /// - /// Type to clone. - /// The object. - /// The object as Json. - public static T Clone(T obj) - { - return ProtocolJsonSerializer.CloneTo(obj); - } - - /// - /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. - /// - /// The object type. - /// Intial object. - /// Overlay object. - /// merged object. - public static T Merge(T startObject, T overlayObject) - where T : class - { - return Assign(startObject, overlayObject); - } - - /// - /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. - /// - /// The target type. - /// intial object of any type. - /// overlay object of any type. - /// merged object. - public static T Assign(object startObject, object overlayObject) - where T : class - { - return (T)Assign(startObject, overlayObject, typeof(T)); - } - - /// - /// Equivalent to javascripts ObjectPath.Assign, creates a new object from startObject overlaying any non-null values from the overlay object. - /// - /// intial object of any type. - /// overlay object of any type. - /// type to output. - /// merged object. - public static object Assign(object startObject, object overlayObject, Type type) - { - if (startObject != null && overlayObject != null) - { - // make a deep clone JObject of the startObject - var jsMerged = startObject is JsonObject ? (JsonObject)(startObject as JsonObject).DeepClone() : JsonSerializer.SerializeToNode(startObject, _serializerOptions); - - // get a JObject of the overlay object - var jsOverlay = overlayObject is JsonObject ? overlayObject as JsonObject : JsonSerializer.SerializeToNode(overlayObject, _serializerOptions); - - var merged = ObjectMerge.Merge(jsMerged, jsOverlay); - - /* - jsMerged.Merge(jsOverlay, new JsonMergeSettings - { - MergeArrayHandling = MergeArrayHandling.Replace, - MergeNullValueHandling = MergeNullValueHandling.Ignore, - }); - */ - - return merged.Deserialize(type, _serializerOptions); - } - - var singleObject = startObject ?? overlayObject; - if (singleObject != null) - { - if (singleObject is JsonObject) - { - return (singleObject as JsonObject).Deserialize(type); - } - - return singleObject; - } - - return (Type)Activator.CreateInstance(type); - } - - /// - /// Convert a generic object to a typed object. - /// - /// type to convert to. - /// value to convert. - /// converted value. - public static T MapValueTo(object val) - { - if (val is JsonElement) - { - T result = ProtocolJsonSerializer.ToObject(val); - return result; - } - - if (typeof(T) == typeof(object)) - { - return (T)val; - } - - if (val is JsonValue valValue) - { - if (valValue.TryGetValue(out T value)) - { - return value; - } - - if (valValue.GetValueKind() == JsonValueKind.Number && typeof(T) == typeof(string)) - { - var number = valValue.GetValue(); - return ProtocolJsonSerializer.ToObject(number.ToString()); - } - - if (valValue.GetValueKind() == JsonValueKind.String && typeof(T) == typeof(int)) - { - var str = valValue.GetValue(); - return ProtocolJsonSerializer.ToObject(str); - } - } - - if (val is JsonNode valNode) - { - return valNode.Deserialize(_serializerOptions); - } - - if (typeof(T) == typeof(JsonNode)) - { - return JsonSerializer.Deserialize(JsonSerializer.Serialize(val, _serializerOptions), _serializerOptions); - } - - if (val is T t) - { - return t; - } - - if (val is short || val is int || val is long || - val is ushort || val is uint || val is ulong || - val is decimal || val is float || val is double) - { - return (T)Convert.ChangeType(val, typeof(T)); - } - - var json = JsonSerializer.Serialize(val, _serializerOptions); - return JsonSerializer.Deserialize(json, _serializerOptions); - } - - /// - /// Given an root object and property path, resolve to a constant if eval = true or a constant path otherwise. - /// conversation[user.name][user.age] => ['conversation', 'joe', 32]. - /// - /// root object. - /// property path to resolve. - /// Path segments. - /// True to evaluate resulting segments. - /// True if it was able to resolve all nested references. - public static bool TryResolvePath(object obj, string propertyPath, out List segments, bool eval = false) - { - var soFar = new List(); - segments = soFar; - var first = propertyPath.Length > 0 ? propertyPath[0] : ' '; - if (first == '\'' || first == '"') - { - if (!propertyPath.EndsWith(first.ToString(), StringComparison.Ordinal)) - { - return false; - } - - soFar.Add(propertyPath.Substring(1, propertyPath.Length - 2)); - } - else if (int.TryParse(propertyPath, out var number)) - { - soFar.Add(number); - } - else - { - var start = 0; - int i; - - // Emit current fragment - void Emit() - { - var segment = propertyPath.Substring(start, i - start); - if (!string.IsNullOrEmpty(segment)) - { - soFar.Add(segment); - } - - start = i + 1; - } - - // Scan path evaluating as we go - for (i = 0; i < propertyPath.Length; ++i) - { - var ch = propertyPath[i]; - if (ch == '.' || ch == '[') - { - Emit(); - } - - if (ch == '[') - { - // Bracket expression - var nesting = 1; - while (++i < propertyPath.Length) - { - ch = propertyPath[i]; - if (ch == '[') - { - ++nesting; - } - else if (ch == ']') - { - --nesting; - if (nesting == 0) - { - break; - } - } - } - - if (nesting > 0) - { - // Unbalanced brackets - return false; - } - - var expr = propertyPath.Substring(start, i - start); - start = i + 1; - if (!TryResolvePath(obj, expr, out var indexer, true) || indexer.Count != 1) - { - // Could not resolve bracket expression - return false; - } - - var result = MapValueTo(indexer.First()); - if (int.TryParse(result, out var index)) - { - soFar.Add(index); - } - else - { - soFar.Add(result); - } - } - } - - Emit(); - - if (eval) - { - if (!ResolveSegments(obj, soFar, out var result)) - { - return false; - } - - soFar.Clear(); - soFar.Add(MapValueTo(result)); - } - } - - return true; - } - - private static bool ResolveSegment(ref dynamic current, object segment) - { - if (current != null) - { - if (segment is int index) - { - current = current[index]; - } - else - { - current = GetObjectProperty(current, segment as string); - } - - // This interprets any null value as not being present. - return (object)current != null; - } - - return false; - } - - private static bool ResolveSegments(dynamic current, List segments, out object result) - { - result = current; - foreach (var segment in segments) - { - if (!ResolveSegment(ref result, segment)) - { - return false; - } - } - - return true; - } - - /// - /// Get a property or array element from an object. - /// - /// object. - /// property or array segment to get relative to the object. - /// the value or null if not found. - private static object GetObjectProperty(object obj, string property) - { - if (obj == null) - { - return null; - } - - if (obj is IDictionary dict) - { - var key = dict.Keys.Where(key => string.Equals(key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? property; - if (dict.TryGetValue(key, out var value)) - { - return value; - } - - return null; - } - - if (obj is JsonObject jobj) - { - var key = jobj.AsEnumerable().Where(kvPair => string.Equals(kvPair.Key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Key ?? property; - jobj.TryGetPropertyValue(key, out var value); - return value; - } - - if (obj is JsonValue jval) - { - // in order to make things like "this.value.Length" work, when "this.value" is a string. - //return GetObjectProperty(JsonSerializer.SerializeToNode(jval), property); - throw new NotImplementedException(); - } - - var prop = obj.GetType().GetProperties().Where(p => string.Equals(p.Name, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - if (prop != null) - { - return prop.GetValue(obj); - } - - return null; - } - - /// - /// Given an object, set a property or array element on it with a value. - /// - /// object to modify. - /// property or array segment to put the value in. - /// value to store. - /// if true, value will be normalized to JSON primitive objects. - private static void SetObjectSegment(object obj, object segment, object value, bool json = true) - { - object val; - - val = GetNormalizedValue(value, json); - if (segment is int index) - { - if (obj is JsonArray jarray) - { - // grow array if required - var jar = obj as JsonArray; - for (var i = jar.Count; i <= index; i++) - { - jar.Add(null); - } - - jar[index] = JsonSerializer.SerializeToNode(val); - } - else if (obj is Array array) - { - if (index >= array.Length) - { - // TODO - throw new ArgumentException("Cannot grow arrays yet"); - } - - array.SetValue(value, index); - } - else if (obj is IList list) - { - if (index >= list.Count) - { - for (var i = list.Count; i <= index; i++) - { - list.Add(null); - } - } - - list[index] = value; - } - return; - } - - var property = segment as string; - if (obj is IDictionary dict) - { - // For case insensitive key - var key = dict.Keys.Where(k => string.Equals(k, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? property; - dict[key] = value; - return; - } - - if (obj is JsonObject jobj) - { - // For case insensitive key - var key = jobj.Where(p => string.Equals(p.Key, property, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Key ?? property; - jobj[key] = val is JsonNode valNode ? valNode : JsonSerializer.SerializeToNode(val); - return; - } - - var prop = obj.GetType().GetProperty(property); - if (prop != null) - { - if (prop.GetValue(obj) != value) - { - prop.SetValue(obj, value); - } - } - } - - /// - /// Normalize value as json objects. - /// - /// value to normalize. - /// normalize as json objects. - /// normalized value. - private static object GetNormalizedValue(object value, bool json) - { - object val; - if (json) - { - if (value is JsonNode node) - { - val = node.DeepClone(); - } - else if (value is JsonElement) - { - val = JsonSerializer.SerializeToNode(value, _serializerOptions); - } - else if (value == null) - { - val = null; - } - else if (value is string || value is byte || value is bool || - value is DateTime || value is DateTimeOffset || - value is short || value is int || value is long || - value is ushort || value is uint || value is ulong || - value is decimal || value is float || value is double) - { - val = JsonValue.Create(value); - } - else - { - var jsonValue = JsonSerializer.Serialize(value, _serializerOptions); - val = JsonObject.Parse(jsonValue, new JsonNodeOptions() { PropertyNameCaseInsensitive = true }); - } - } - else - { - val = value; - } - - return val; - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs b/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs deleted file mode 100644 index c7c0dfa4..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/PrivateConversationState.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Storage; -using System; - -namespace Microsoft.Agents.State -{ - /// - /// Defines a state keyed to a conversation and user. - /// - /// - /// Conversation state is available in any turn in a specific conversation, regardless of user, - /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. - /// - /// The storage layer to use. - public class PrivateConversationState(IStorage storage) : BotState(storage, ScopeName) - { - public static readonly string ScopeName = "private"; - - /// - /// Gets the key to use when reading and writing state to and from storage. - /// - /// The context object for this turn. - /// The storage key. - /// - /// Private conversation state includes the channel ID, conversation ID, and user ID as part - /// of its storage key. - /// - protected override string GetStorageKey(ITurnContext turnContext) - { - var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); - var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); - var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); - return $"{channelId}/conversations/{conversationId}/users/{userId}"; - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/README.md b/src/libraries/Core/Microsoft.Agents.State/README.md deleted file mode 100644 index 6f8aa66d..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Microsoft.Agents.State - -## About - -A bot is inherently stateless. Once your bot is deployed, it may not run in the same process or on the same machine from one turn to the next. However, your bot may need to track the context of a conversation so that it can manage its behavior and remember answers to previous questions. The state and storage features of the Agents SDK allow you to add state to your bot. Bots use state management and storage objects to manage and persist state. The state manager provides an abstraction layer that lets you access state properties using property accessors, independent of the type of underlying storage. diff --git a/src/libraries/Core/Microsoft.Agents.State/TempState.cs b/src/libraries/Core/Microsoft.Agents.State/TempState.cs deleted file mode 100644 index c89000c6..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/TempState.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.State -{ - public class TempState : IBotState - { - public static readonly string ScopeName = "temp"; - private readonly Dictionary _state = []; - - public const string AuthScopeKey = "AuthScope"; - public const string InvokeResponseKey = "InvokeResponse"; - public const string BotIdentityKey = "BotIdentity"; - - public string Name => ScopeName; - - public void ClearState() - { - _state.Clear(); - } - - public Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) - { - _state.Clear(); - return Task.CompletedTask; - } - - public bool HasValue(string name) - { - return ObjectPath.HasValue(_state, name); - } - - public void DeleteValue(string name) - { - ObjectPath.RemovePathValue(_state, name); - } - - public T GetValue(string name, Func defaultValueFactory = null) - { - T result = default; - - try - { - result = ObjectPath.GetPathValue(_state, name, true); - if (result == null) - { - if (defaultValueFactory != null) - { - result = defaultValueFactory(); - } - SetValue(name, result); - } - } - catch (KeyNotFoundException) - { - if (defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - SetValue(name, result); - } - } - - return result; - } - - public bool IsLoaded() - { - return true; - } - - public Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public void SetValue(string name, T value) - { - ObjectPath.SetPathValue(_state, name, value); - } - } -} diff --git a/src/libraries/Core/Microsoft.Agents.State/UserState.cs b/src/libraries/Core/Microsoft.Agents.State/UserState.cs deleted file mode 100644 index d0a13feb..00000000 --- a/src/libraries/Core/Microsoft.Agents.State/UserState.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Interfaces; -using Microsoft.Agents.Storage; -using System; - -namespace Microsoft.Agents.State -{ - /// - /// Defines a state keyed to a user. - /// - /// - /// Conversation state is available in any turn in a specific conversation, regardless of user, - /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. - /// - /// The storage layer to use. - public class UserState(IStorage storage) : BotState(storage, ScopeName) - { - public static readonly string ScopeName = "user"; - - /// - /// Gets the key to use when reading and writing state to and from storage. - /// - /// The context object for this turn. - /// The storage key. - /// - /// User state includes the channel ID and user ID as part of its storage key. - /// - protected override string GetStorageKey(ITurnContext turnContext) - { - var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); - var userId = turnContext.Activity.From?.Id ?? throw new InvalidOperationException("invalid activity-missing From.Id"); - return $"{channelId}/users/{userId}"; - } - } -} diff --git a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs index 37ed2d2b..c6e7692e 100644 --- a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs +++ b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs @@ -42,10 +42,9 @@ public class CloudAdapter public CloudAdapter( IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, - ITurnState state = null, ILogger logger = null, bool async = true, - BotBuilder.IMiddleware[] middlewares = null) : base(channelServiceClientFactory, state, logger: logger) + BotBuilder.IMiddleware[] middlewares = null) : base(channelServiceClientFactory, logger: logger) { _activityTaskQueue = activityTaskQueue ?? throw new ArgumentNullException(nameof(activityTaskQueue)); _async = async; diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs index 83770d91..3a88cf48 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs @@ -143,7 +143,7 @@ private async Task ExchangedTokenAsync(ITurnContext turnContext, Cancellat try { - var userTokenClient = turnContext.TurnState.Temp.GetValue(); + var userTokenClient = turnContext.Services.Get(); if (userTokenClient != null) { tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync( diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs similarity index 61% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs index 99e47fef..7e091534 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ConfigHandlerAsync.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs @@ -1,20 +1,19 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Function for handling config events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The config data. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of ConfigResponseBase. - public delegate Task ConfigHandlerAsync(ITurnContext turnContext, TState turnState, object configData, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task ConfigHandlerAsync(ITurnContext turnContext, ITurnState turnState, object configData, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs index 8360c429..4442445b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopData.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs @@ -1,5 +1,5 @@  -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Data returned when the thumbsup or thumbsdown button is clicked and response is received. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs similarity index 61% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs index 3821a720..da19002b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FeedbackLoopHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs @@ -1,19 +1,18 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Function for feedback loop activites /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The feedback loop data. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task FeedbackLoopHandler(ITurnContext turnContext, TState turnState, FeedbackLoopData feedbackLoopData, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task FeedbackLoopHandler(ITurnContext turnContext, ITurnState turnState, FeedbackLoopData feedbackLoopData, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs similarity index 65% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs index 9d4d157f..0416f5d9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/FileConsentCardHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs @@ -1,20 +1,19 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Function for handling file consent card activities. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The response representing the value of the invoke activity sent when the user acts on a file consent card. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task FileConsentHandler(ITurnContext turnContext, TState turnState, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task FileConsentHandler(ITurnContext turnContext, ITurnState turnState, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs similarity index 67% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs index 847e3df2..0c60f84d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/IInputFileDownloader.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs @@ -1,17 +1,16 @@  using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// A plugin responsible for downloading files relative to the current user's input. /// - /// Type of application state. - public interface IInputFileDownloader where TState : TurnState, new() + public interface IInputFileDownloader { /// /// Download any files relative to the current user's input. @@ -20,6 +19,6 @@ namespace Microsoft.Agents.Teams.Application /// The turn state. /// The cancellation token. /// A list of input files - public Task> DownloadFilesAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken = default); + public Task> DownloadFilesAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken = default); } } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs index 50ce83d0..bf2428f9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/InputFile.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs @@ -1,7 +1,7 @@  using System; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Represents an upload file diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs similarity index 81% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs index aca159db..bd33e035 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/Meetings.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs @@ -1,27 +1,24 @@ -using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; using System; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.Meetings +namespace Microsoft.Agents.Teams.App.Meetings { /// /// Meetings class to enable fluent style registration of handlers related to Microsoft Teams Meetings. /// - /// The type of the turn state object used by the application. - public class Meetings - where TState : TurnState, new() + public class MeetingsFeature { - private readonly Application _app; + private readonly Application _app; /// /// Creates a new instance of the Meetings class. /// /// The top level application class to register handlers with. - public Meetings(Application app) + public MeetingsFeature(Application app) { this._app = app; } @@ -31,7 +28,7 @@ public Meetings(Application app) /// /// Function to call when a Microsoft Teams meeting start event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnStart(MeetingStartHandler handler) + public Application OnStart(MeetingStartHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -40,7 +37,7 @@ public Application OnStart(MeetingStartHandler handler) && string.Equals(context.Activity?.ChannelId, Channels.Msteams) && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingStart") ); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { MeetingStartEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); @@ -54,7 +51,7 @@ public Application OnStart(MeetingStartHandler handler) /// /// Function to call when a Microsoft Teams meeting end event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnEnd(MeetingEndHandler handler) + public Application OnEnd(MeetingEndHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -63,7 +60,7 @@ public Application OnEnd(MeetingEndHandler handler) && string.Equals(context.Activity?.ChannelId, Channels.Msteams) && string.Equals(context.Activity?.Name, "application/vnd.microsoft.meetingEnd") ); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { MeetingEndEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); @@ -77,7 +74,7 @@ public Application OnEnd(MeetingEndHandler handler) /// /// Function to call when a Microsoft Teams meeting participants join event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) + public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -86,7 +83,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); @@ -100,7 +97,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler /// Function to call when a Microsoft Teams meeting participants leave event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) + public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -109,7 +106,7 @@ public Application OnParticipantsLeave(MeetingParticipantsEventHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, meeting, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs similarity index 64% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs index a6a681c0..08dd9510 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/Meetings/MeetingsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs @@ -1,45 +1,42 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.Meetings +namespace Microsoft.Agents.Teams.App.Meetings { /// /// Function for handling Microsoft Teams meeting start events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The details of the meeting. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task MeetingStartHandler(ITurnContext turnContext, TState turnState, MeetingStartEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task MeetingStartHandler(ITurnContext turnContext, ITurnState turnState, MeetingStartEventDetails meeting, CancellationToken cancellationToken); /// /// Function for handling Microsoft Teams meeting end events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The details of the meeting. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task MeetingEndHandler(ITurnContext turnContext, TState turnState, MeetingEndEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task MeetingEndHandler(ITurnContext turnContext, ITurnState turnState, MeetingEndEventDetails meeting, CancellationToken cancellationToken); /// /// Function for handling Microsoft Teams meeting participants join or leave events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The details of the meeting. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task MeetingParticipantsEventHandler(ITurnContext turnContext, TState turnState, MeetingParticipantsEventDetails meeting, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task MeetingParticipantsEventHandler(ITurnContext turnContext, ITurnState turnState, MeetingParticipantsEventDetails meeting, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs similarity index 86% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index 46683cd6..865a5747 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -1,7 +1,7 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.MessageExtensions +namespace Microsoft.Agents.Teams.App.MessageExtensions { /// /// Constants for message extension invoke names @@ -39,9 +39,7 @@ public class MessageExtensionsInvokeNames /// /// MessageExtensions class to enable fluent style registration of handlers related to Message Extensions. /// - /// The type of the turn state object used by the application. - public class MessageExtensions - where TState : TurnState, new() + public class MessageExtensionsFeature { private static readonly string SUBMIT_ACTION_INVOKE_NAME = "composeExtension/submitAction"; private static readonly string SELECT_ITEM_INVOKE_NAME = "composeExtension/selectItem"; @@ -49,13 +47,13 @@ public class MessageExtensions private static readonly string QUERY_SETTING_URL = "composeExtension/querySettingUrl"; private static readonly string QUERY_CARD_BUTTON_CLICKED = "composeExtension/onCardButtonClicked"; - private readonly Application _app; + private readonly Application _app; /// /// Creates a new instance of the MessageExtensions class. /// /// The top level application class to register handlers with. - public MessageExtensions(Application app) + public MessageExtensionsFeature(Application app) { this._app = app; } @@ -66,7 +64,7 @@ public MessageExtensions(Application app) /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) + public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -80,7 +78,7 @@ public Application OnSubmitAction(string commandId, SubmitActionHandlerA /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) + public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -94,10 +92,10 @@ public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHa /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmitAction(RouteSelectorAsync routeSelector, SubmitActionHandlerAsync handler) + public Application OnSubmitAction(RouteSelectorAsync routeSelector, SubmitActionHandlerAsync handler) { MessagingExtensionAction? messagingExtensionAction; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_ACTION_INVOKE_NAME) @@ -109,7 +107,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.Data, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -125,7 +123,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, Subm /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) + public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -160,7 +158,7 @@ public Application OnSubmitAction(MultipleRouteSelector routeSelectors, /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) + public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -175,7 +173,7 @@ public Application OnBotMessagePreviewEdit(string commandId, BotMessageP /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) + public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -190,9 +188,9 @@ public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMe /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, BotMessagePreviewEditHandlerAsync handler) + public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, BotMessagePreviewEditHandlerAsync handler) { - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { MessagingExtensionAction? messagingExtensionAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -206,7 +204,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec MessagingExtensionActionResponse result = await handler(turnContext, turnState, messagingExtensionAction.BotActivityPreview[0], cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -223,7 +221,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelec /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) + public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -258,7 +256,7 @@ public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSe /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) + public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -273,7 +271,7 @@ public Application OnBotMessagePreviewSend(string commandId, BotMessageP /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) + public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -288,11 +286,11 @@ public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMe /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) + public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { MessagingExtensionAction? messagingExtensionAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -307,7 +305,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec await handler(turnContext, turnState, activityPreview, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new(); Activity activity = ActivityUtilities.CreateInvokeResponseActivity(response); @@ -325,7 +323,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelec /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) + public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -359,7 +357,7 @@ public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSe /// ID of the commands to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) + public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -373,7 +371,7 @@ public Application OnFetchTask(string commandId, FetchTaskHandlerAsyncRegular expression to match against the ID of the commands to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) + public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -387,11 +385,11 @@ public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerA /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) + public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.FETCH_TASK_INVOKE_NAME)) @@ -402,7 +400,7 @@ public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTa TaskModuleResponse result = await handler(turnContext, turnState, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -418,7 +416,7 @@ public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTa /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) + public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -452,7 +450,7 @@ public Application OnFetchTask(MultipleRouteSelector routeSelectors, Fet /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnQuery(string commandId, QueryHandlerAsync handler) + public Application OnQuery(string commandId, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -466,7 +464,7 @@ public Application OnQuery(string commandId, QueryHandlerAsync h /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) + public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -480,11 +478,11 @@ public Application OnQuery(Regex commandIdPattern, QueryHandlerAsyncFunction that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) + public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { MessagingExtensionQuery? messagingExtensionQuery; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -503,7 +501,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle MessagingExtensionResult result = await handler(turnContext, turnState, query, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -523,7 +521,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandle /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) + public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -563,7 +561,7 @@ public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHa /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnSelectItem(SelectItemHandlerAsync handler) + public Application OnSelectItem(SelectItemHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -571,12 +569,12 @@ public Application OnSelectItem(SelectItemHandlerAsync handler) return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, SELECT_ITEM_INVOKE_NAME)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { MessagingExtensionResult result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -595,7 +593,7 @@ public Application OnSelectItem(SelectItemHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnQueryLink(QueryLinkHandlerAsync handler) + public Application OnQueryLink(QueryLinkHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -603,13 +601,13 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.QUERY_LINK_INVOKE_NAME)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { AppBasedLinkQuery? appBasedLinkQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -633,7 +631,7 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) + public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -641,13 +639,13 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync ha return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, MessageExtensionsInvokeNames.ANONYMOUS_QUERY_LINK_INVOKE_NAME)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { AppBasedLinkQuery? appBasedLinkQuery = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); MessagingExtensionResult result = await handler(turnContext, turnState, appBasedLinkQuery!.Url, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -669,7 +667,7 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync ha /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) + public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -677,12 +675,12 @@ public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, QUERY_SETTING_URL)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { MessagingExtensionResult result = await handler(turnContext, turnState, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { MessagingExtensionActionResponse response = new() { @@ -704,7 +702,7 @@ public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigureSettings(ConfigureSettingsHandler handler) + public Application OnConfigureSettings(ConfigureSettingsHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -712,12 +710,12 @@ public Application OnConfigureSettings(ConfigureSettingsHandler return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, CONFIGURE_SETTINGS)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -737,7 +735,7 @@ public Application OnConfigureSettings(ConfigureSettingsHandler /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnCardButtonClicked(CardButtonClickedHandler handler) + public Application OnCardButtonClicked(CardButtonClickedHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -745,12 +743,12 @@ public Application OnCardButtonClicked(CardButtonClickedHandler return Task.FromResult(string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, QUERY_CARD_BUTTON_CLICKED)); }; - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs similarity index 68% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs index c6d5b1b4..e57cc3fa 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/MessageExtensions/MessageExtensionsHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs @@ -1,129 +1,119 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.AdaptiveCards; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Teams.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.MessageExtensions +namespace Microsoft.Agents.Teams.App.MessageExtensions { /// /// Function for handling Message Extension submitAction events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The data that was submitted. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionActionResponse. - public delegate Task SubmitActionHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task SubmitActionHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken); /// /// Function for handling Message Extension botMessagePreview edit events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The activity that's being previewed by the user. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionActionResponse. - public delegate Task BotMessagePreviewEditHandlerAsync(ITurnContext turnContext, TState turnState, IActivity activityPreview, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task BotMessagePreviewEditHandlerAsync(ITurnContext turnContext, ITurnState turnState, IActivity activityPreview, CancellationToken cancellationToken); /// /// Function for handling Message Extension botMessagePreview send events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The activity that's being previewed by the user. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task BotMessagePreviewSendHandler(ITurnContext turnContext, TState turnState, IActivity activityPreview, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task BotMessagePreviewSendHandler(ITurnContext turnContext, ITurnState turnState, IActivity activityPreview, CancellationToken cancellationToken); /// /// Function for handling Message Extension fetchTask events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of TaskModuleResponse. - public delegate Task FetchTaskHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task FetchTaskHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken); /// /// Function for handling Message Extension query events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The query parameters that were sent by the client. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionResult. - public delegate Task QueryHandlerAsync(ITurnContext turnContext, TState turnState, Query> query, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task QueryHandlerAsync(ITurnContext turnContext, ITurnState turnState, Query> query, CancellationToken cancellationToken); /// /// Function for handling Message Extension selecting item events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The item that was selected. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionResult. - public delegate Task SelectItemHandlerAsync(ITurnContext turnContext, TState turnState, object item, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task SelectItemHandlerAsync(ITurnContext turnContext, ITurnState turnState, object item, CancellationToken cancellationToken); /// /// Function for handling Message Extension link unfurling events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The URL that should be unfurled. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionResult. - public delegate Task QueryLinkHandlerAsync(ITurnContext turnContext, TState turnState, string url, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task QueryLinkHandlerAsync(ITurnContext turnContext, ITurnState turnState, string url, CancellationToken cancellationToken); /// /// Function for handling Message Extension configuring query setting url events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// An instance of MessagingExtensionResult. - public delegate Task QueryUrlSettingHandlerAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task QueryUrlSettingHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken); /// /// Function for handling Message Extension configuring settings events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The configuration settings that was submitted. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task ConfigureSettingsHandler(ITurnContext turnContext, TState turnState, object settings, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task ConfigureSettingsHandler(ITurnContext turnContext, ITurnState turnState, object settings, CancellationToken cancellationToken); /// /// Function for handling Message Extension clicking card button events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The card data. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task CardButtonClickedHandler(ITurnContext turnContext, TState turnState, object cardData, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task CardButtonClickedHandler(ITurnContext turnContext, ITurnState turnState, object cardData, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs similarity index 63% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs index fc0bf9c9..502ad24d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/O365ConnectorCardActionHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs @@ -1,20 +1,19 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Function for handling O365 Connector Card Action activities. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The O365 connector card HttpPOST invoke query. /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task O365ConnectorCardActionHandler(ITurnContext turnContext, TState turnState, O365ConnectorCardActionQuery query, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task O365ConnectorCardActionHandler(ITurnContext turnContext, ITurnState turnState, O365ConnectorCardActionQuery query, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs similarity index 61% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs index 00818d32..82b0013b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/ReadReceiptHandler.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs @@ -1,19 +1,18 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Function for handling read receipt events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The read receipt data. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - public delegate Task ReadReceiptHandler(ITurnContext turnContext, TState turnState, ReadReceiptInfo data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task ReadReceiptHandler(ITurnContext turnContext, ITurnState turnState, ReadReceiptInfo data, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs similarity index 87% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs index d7cf454d..24c4738c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModules.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs @@ -1,6 +1,6 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Teams.Models; @@ -9,14 +9,12 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.TaskModules +namespace Microsoft.Agents.Teams.App.TaskModules { /// /// TaskModules class to enable fluent style registration of handlers related to Task Modules. /// - /// The type of the turn state object used by the application. - public class TaskModules - where TState : TurnState, new() + public class TaskModulesFeature { private static readonly string FETCH_INVOKE_NAME = "task/fetch"; private static readonly string SUBMIT_INVOKE_NAME = "task/submit"; @@ -24,13 +22,13 @@ public class TaskModules //TODO //private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; - private readonly Application _app; + private readonly Application _app; /// /// Creates a new instance of the TaskModules class. /// /// The top level application class to register handlers with. - public TaskModules(Application app) + public TaskModulesFeature(Application app) { this._app = app; } @@ -41,7 +39,7 @@ public TaskModules(Application app) /// Name of the verb to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(string verb, FetchHandlerAsync handler) + public Application OnFetch(string verb, FetchHandlerAsync handler) { throw new NotImplementedException(); @@ -61,7 +59,7 @@ public Application OnFetch(string verb, FetchHandlerAsync handle /// Regular expression to match against the verbs to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) + public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) { throw new NotImplementedException(); @@ -81,11 +79,11 @@ public Application OnFetch(Regex verbPattern, FetchHandlerAsync /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) + public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { TaskModuleAction? taskModuleAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -98,7 +96,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -115,7 +113,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandle /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) + public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -151,7 +149,7 @@ public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHa /// Name of the verb to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(string verb, SubmitHandlerAsync handler) + public Application OnSubmit(string verb, SubmitHandlerAsync handler) { throw new NotImplementedException(); @@ -172,7 +170,7 @@ public Application OnSubmit(string verb, SubmitHandlerAsync hand /// Regular expression to match against the verbs to register the handler for /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) + public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) { throw new NotImplementedException(); @@ -192,11 +190,11 @@ public Application OnSubmit(Regex verbPattern, SubmitHandlerAsyncFunction that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) + public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { TaskModuleAction? taskModuleAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) @@ -209,7 +207,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand TaskModuleResponse result = await handler(turnContext, turnState, taskModuleAction.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -226,7 +224,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHand /// Combination of String, Regex, and RouteSelectorAsync verb(s) to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) + public Application OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs similarity index 63% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs index 9436e6de..0ce2106e 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesHandlers.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs @@ -1,31 +1,29 @@ using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Application.TaskModules +namespace Microsoft.Agents.Teams.App.TaskModules { /// /// Function for handling Task Module fetch events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The data associated with the fetch. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// An instance of TaskModuleResponse. - public delegate Task FetchHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task FetchHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken); /// /// Function for handling Task Module submit events. /// - /// Type of the turn state. This allows for strongly typed access to the turn state. /// A strongly-typed context object for this turn. /// The turn state object that stores arbitrary data for this turn. /// The data associated with the fetch. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// An instance of TaskModuleResponse. - public delegate Task SubmitHandlerAsync(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken) where TState : TurnState; + public delegate Task SubmitHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken); } diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs similarity index 90% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs index 72af0397..d5721c8b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TaskModules/TaskModulesOptions.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Agents.Teams.Application.TaskModules +namespace Microsoft.Agents.Teams.App.TaskModules { /// /// Options for TaskModules class. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs similarity index 84% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs index 4dde5014..fa32cadb 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsApplication.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs @@ -6,20 +6,18 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder.Application.State; -using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.Teams.Application.Meetings; -using Microsoft.Agents.Teams.Application.MessageExtensions; -using Microsoft.Agents.Teams.Application.TaskModules; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Teams.App.Meetings; +using Microsoft.Agents.Teams.App.MessageExtensions; +using Microsoft.Agents.Teams.App.TaskModules; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Application class for routing and processing incoming requests. /// - /// Type of the turnState. This allows for strongly typed access to the turn turnState. - public class TeamsApplication : Application - where TState : TurnState, new() + public class TeamsApplication : Application { private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; @@ -33,27 +31,27 @@ public class TeamsApplication : Application /// /// Optional. Options used to configure the application. /// - public TeamsApplication(ApplicationOptions options) : base(options) + public TeamsApplication(ApplicationOptions options) : base(options) { - Meetings = new Meetings(this); - MessageExtensions = new MessageExtensions(this); - TaskModules = new TaskModules(this); + Meetings = new MeetingsFeature(this); + MessageExtensions = new MessageExtensionsFeature(this); + TaskModules = new TaskModulesFeature(this); } /// /// Fluent interface for accessing Meetings' specific features. /// - public Meetings Meetings { get; } + public MeetingsFeature Meetings { get; } /// /// Fluent interface for accessing Message Extensions' specific features. /// - public MessageExtensions MessageExtensions { get; } + public MessageExtensionsFeature MessageExtensions { get; } /// /// Fluent interface for accessing Task Modules' specific features. /// - public TaskModules TaskModules { get; } + public TaskModulesFeature TaskModules { get; } /// /// Handles conversation update events. @@ -61,7 +59,7 @@ public TeamsApplication(ApplicationOptions options) : base(options) /// Name of the conversation update event to handle, can use . /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public override Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + public override Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) { ArgumentNullException.ThrowIfNull(conversationUpdateEvent); ArgumentNullException.ThrowIfNull(handler); @@ -139,7 +137,7 @@ public override Application OnConversationUpdate(string conversationUpda /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageEdit(RouteHandler handler) + public Application OnMessageEdit(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -160,7 +158,7 @@ public Application OnMessageEdit(RouteHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageUndelete(RouteHandler handler) + public Application OnMessageUndelete(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -181,7 +179,7 @@ public Application OnMessageUndelete(RouteHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageDelete(RouteHandler handler) + public Application OnMessageDelete(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -202,7 +200,7 @@ public Application OnMessageDelete(RouteHandler handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnTeamsReadReceipt(ReadReceiptHandler handler) + public Application OnTeamsReadReceipt(ReadReceiptHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -211,7 +209,7 @@ public Application OnTeamsReadReceipt(ReadReceiptHandler handler && string.Equals(context.Activity?.ChannelId, Channels.Msteams) && string.Equals(context.Activity?.Name, "application/vnd.microsoft.readReceipt") ); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { ReadReceiptInfo readReceiptInfo = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, readReceiptInfo, cancellationToken); @@ -225,19 +223,19 @@ public Application OnTeamsReadReceipt(ReadReceiptHandler handler /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigFetch(ConfigHandlerAsync handler) + public Application OnConfigFetch(ConfigHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, CONFIG_FETCH_INVOKE_NAME) && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -252,19 +250,19 @@ public Application OnConfigFetch(ConfigHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigSubmit(ConfigHandlerAsync handler) + public Application OnConfigSubmit(ConfigHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(turnContext.Activity.Name, CONFIG_SUBMIT_INVOKE_NAME) && string.Equals(turnContext.Activity.ChannelId, Channels.Msteams)); - RouteHandler routeHandler = async (ITurnContext turnContext, TState turnState, CancellationToken cancellationToken) => + RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { ConfigResponseBase result = await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(result); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -279,7 +277,7 @@ public Application OnConfigSubmit(ConfigHandlerAsync handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFileConsentAccept(FileConsentHandler handler) + public Application OnFileConsentAccept(FileConsentHandler handler) => OnFileConsent(handler, "accept"); /// @@ -287,10 +285,10 @@ public Application OnFileConsentAccept(FileConsentHandler handle /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFileConsentDecline(FileConsentHandler handler) + public Application OnFileConsentDecline(FileConsentHandler handler) => OnFileConsent(handler, "decline"); - private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) + private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => @@ -304,13 +302,13 @@ private Application OnFileConsent(FileConsentHandler handler, st && string.Equals(fileConsentCardResponse.Action, fileConsentAction) ); }; - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { FileConsentCardResponse fileConsentCardResponse = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, fileConsentCardResponse, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -325,7 +323,7 @@ private Application OnFileConsent(FileConsentHandler handler, st /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) + public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -333,13 +331,13 @@ public Application OnO365ConnectorCardAction(O365ConnectorCardActionHand string.Equals(context.Activity?.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) && string.Equals(context.Activity?.Name, "actionableMessage/executeAction") ); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { O365ConnectorCardActionQuery query = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, query, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); @@ -355,7 +353,7 @@ public Application OnO365ConnectorCardAction(O365ConnectorCardActionHand /// /// Function to cal lwhen the route is triggered /// - public Application OnFeedbackLoop(FeedbackLoopHandler handler) + public Application OnFeedbackLoop(FeedbackLoopHandler handler) { ArgumentNullException.ThrowIfNull(handler); @@ -371,7 +369,7 @@ public Application OnFeedbackLoop(FeedbackLoopHandler handler) ); }; - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { FeedbackLoopData feedbackLoopData = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)!; feedbackLoopData.ReplyToId = turnContext.Activity.ReplyToId; @@ -379,7 +377,7 @@ public Application OnFeedbackLoop(FeedbackLoopHandler handler) await handler(turnContext, turnState, feedbackLoopData, cancellationToken); // Check to see if an invoke response has already been added - if (!turnContext.TurnState.Temp.HasValue(ChannelAdapter.InvokeResponseKey)) + if (!turnContext.StackState.Has(ChannelAdapter.InvokeResponseKey)) { Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs b/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs rename to src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs index 6a22b36d..9d842c59 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Application/TeamsConversationUpdateEvents.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs @@ -1,6 +1,6 @@ -using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.App; -namespace Microsoft.Agents.Teams.Application +namespace Microsoft.Agents.Teams.App { /// /// Conversation update events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs index eb94325c..b0ba17c1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs @@ -127,7 +127,7 @@ private async Task ExchangedTokenAsync(ITurnContext turnContext, Cancellat try { - var userTokenClient = turnContext.TurnState.Temp.GetValue(); + var userTokenClient = turnContext.Services.Get(); if (userTokenClient != null) { tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync( diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs index 0b293ce5..44fdebe5 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs +++ b/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs @@ -450,7 +450,7 @@ private static async Task> GetMembersAsync(ICon private static IConnectorClient GetConnectorClient(ITurnContext turnContext) { - return turnContext.TurnState.Temp.GetValue() ?? throw new InvalidOperationException("This method requires a connector client."); + return turnContext.Services.Get() ?? throw new InvalidOperationException("This method requires a connector client."); } private static async Task GetMemberAsync(IConnectorClient connectorClient, string userId, string conversationId, CancellationToken cancellationToken) diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index 269c47b8..d918de40 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -1,16 +1,15 @@ -using EchoBot.Model; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application; -using Microsoft.Agents.BotBuilder.Application.State; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System.Threading; using System.Threading.Tasks; namespace EchoBot { - public class EchoBotApplication : Application + public class EchoBotApplication : Application { - public EchoBotApplication(ApplicationOptions options) : base(options) + public EchoBotApplication(ApplicationOptions options) : base(options) { OnConversationUpdate("membersAdded", WelcomeMessageAsync); @@ -24,7 +23,7 @@ public EchoBotApplication(ApplicationOptions options) : base(options) /// /// Handles members added events. /// - public static async Task WelcomeMessageAsync(ITurnContext turnContext, TurnState turnState, CancellationToken cancellationToken) + public static async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { foreach (ChannelAccount member in turnContext.Activity.MembersAdded) { @@ -38,23 +37,21 @@ public static async Task WelcomeMessageAsync(ITurnContext turnContext, TurnState /// /// Handles "/reset" message. /// - public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { - turnState.DeleteConversationState(); + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); } /// /// Handles messages except "/reset". /// - public static async Task MessageHandlerAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) + public static async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { - //int count = turnState.Conversation.MessageCount; - int count = turnState.MessageCount(); + int count = turnState.Conversation.MessageCount(); // Increment count state. - //turnState.Conversation.MessageCount = ++count; - turnState.MessageCount(++count); + turnState.Conversation.MessageCount(++count); await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); } diff --git a/src/samples/Application/messaging.echoBot/Model/AppState.cs b/src/samples/Application/messaging.echoBot/Model/AppState.cs deleted file mode 100644 index ba012510..00000000 --- a/src/samples/Application/messaging.echoBot/Model/AppState.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using System; - -namespace EchoBot.Model -{ - // Extend the turn state by configuring custom strongly typed state classes. - public class AppState : TurnState - { - public AppState() : base() - { - ScopeDefaults[CONVERSATION_SCOPE] = new ConversationState(); - } - - /// - /// Stores all the conversation-related state. - /// - public new ConversationState Conversation - { - get - { - TurnStateEntry scope = GetScope(CONVERSATION_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - return (ConversationState)scope.Value!; - } - set - { - TurnStateEntry scope = GetScope(CONVERSATION_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - scope.Replace(value!); - } - } - } - - // This class adds custom properties to the turn state which will be accessible in the activity handler methods. - public class ConversationState : Record - { - private const string _countKey = "countKey"; - - public int MessageCount - { - get => Get(_countKey); - set => Set(_countKey, value); - } - } -} diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs index 780a447a..c6c01ce9 100644 --- a/src/samples/Application/messaging.echoBot/Program.cs +++ b/src/samples/Application/messaging.echoBot/Program.cs @@ -1,8 +1,8 @@ using EchoBot; -using EchoBot.Model; using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Application; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -34,14 +34,9 @@ // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. builder.Services.AddTransient(sp => { - IStorage storage = sp.GetService(); - ApplicationOptions applicationOptions = new() + ApplicationOptions applicationOptions = new() { - Storage = storage, - TurnStateFactory = () => - { - return new AppState(); - } + TurnStateFactory = () => new TurnState(sp.GetService()) }; return new EchoBotApplication(applicationOptions); diff --git a/src/samples/Application/messaging.echoBot/StateExtensions.cs b/src/samples/Application/messaging.echoBot/StateExtensions.cs index 761def4a..c46e0c4c 100644 --- a/src/samples/Application/messaging.echoBot/StateExtensions.cs +++ b/src/samples/Application/messaging.echoBot/StateExtensions.cs @@ -1,19 +1,18 @@ -using Microsoft.Agents.BotBuilder.Application.State; -using System.Diagnostics.Metrics; + +using Microsoft.Agents.BotBuilder.State; namespace EchoBot { public static class StateExtensions { - public static int MessageCount(this TurnState state) + public static int MessageCount(this ConversationState state) { - var value = state.GetValue("conversation.countKey"); - return value == null ? 0 : (int)value; + return state.GetValue("countKey"); } - public static void MessageCount(this TurnState state, int value) + public static void MessageCount(this ConversationState state, int value) { - state.SetValue("conversation.countKey", value); + state.SetValue("countKey", value); } } } diff --git a/src/samples/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/BotToBot/Bot1/Bots/Bot1.cs index 466c5beb..ba018fb8 100644 --- a/src/samples/BotToBot/Bot1/Bots/Bot1.cs +++ b/src/samples/BotToBot/Bot1/Bots/Bot1.cs @@ -125,7 +125,7 @@ private async Task SendToBot(ITurnContext turnContext, IChannelInfo targetChanne // Create a conversationId to interact with the skill and send the activity var options = new ConversationIdFactoryOptions { - FromBotOAuthScope = turnContext.TurnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = turnContext.StackState.Get(ChannelAdapter.OAuthScopeKey), FromBotId = _channelHost.HostAppId, Activity = turnContext.Activity, Bot = targetChannel diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs index 17adb98c..51e2ac07 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs @@ -81,7 +81,7 @@ private async Task InterruptAsync( if (text != null && text.IndexOf("logout") >= 0) { // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); + var userTokenClient = innerDc.Context.Services.Get(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index 660e94e4..0dc1bab8 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -37,7 +37,7 @@ // Create the Conversation state. builder.Services.AddSingleton(); -builder.Services.AddTransient((sp) => +builder.Services.AddSingleton((sp) => { return [ diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs index 9f022920..9f43bcbe 100644 --- a/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs @@ -82,7 +82,7 @@ private async Task InterruptAsync( if (text.IndexOf("logout") >= 0) { // The UserTokenClient encapsulates the authentication processes. - var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); + var userTokenClient = innerDc.Context.Services.Get(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); diff --git a/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs b/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs index 8ef26e70..a2f3edc2 100644 --- a/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs +++ b/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs @@ -53,7 +53,7 @@ public class LogoutDialog(string id, string connectionName) : ComponentDialog(id if (text.IndexOf("logout") >= 0) { // Accesses the UserTokenClient to handle the sign-out process. - var userTokenClient = innerDc.Context.TurnState.Temp.GetValue(); + var userTokenClient = innerDc.Context.Services.Get(); await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); // Sends a confirmation message and cancels any active dialogs. diff --git a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs index 7a8e58fa..533b1ad7 100644 --- a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs +++ b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs @@ -692,7 +692,7 @@ public virtual TurnContext CreateTurnContext(IActivity activity) { var turnContext = new TurnContext(this, activity); - turnContext.TurnState.Temp.SetValue(this); + turnContext.Services.Set(this); return turnContext; } diff --git a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs index 09546d29..1f9049b2 100644 --- a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs +++ b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs @@ -56,7 +56,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) { var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); - context.TurnState.Temp.SetValue(_stopWatchStateKey, stopwatch); + context.StackState.Set(_stopWatchStateKey, stopwatch); await LogIncomingActivityAsync(context, context.Activity, cancellationToken).ConfigureAwait(false); context.OnSendActivities(OnSendActivitiesAsync); @@ -98,7 +98,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) /// A task that represents the work to execute. protected virtual Task LogOutgoingActivityAsync(ITurnContext context, IActivity activity, CancellationToken cancellationToken = default(CancellationToken)) { - var stopwatch = context.TurnState.Temp.GetValue(_stopWatchStateKey); + var stopwatch = context.StackState.Get(_stopWatchStateKey); var actor = "Bot: "; if (activity.Type == ActivityTypes.Message) { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 852463b3..50b30481 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -143,7 +143,7 @@ public async Task RunAsyncShouldSetTelemetryClient() using (var turnContext = new TurnContext(adapter.Object, activity)) { await conversationState.LoadAsync(turnContext, false); - turnContext.TurnState.Temp.SetValue(telemetryClientMock.Object); + turnContext.StackState.Set(telemetryClientMock.Object); await DialogExtensions.RunAsync(dialog, turnContext, conversationState, CancellationToken.None); } @@ -184,20 +184,20 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); - turnContext.TurnState.Temp.SetValue(ChannelAdapter.BotIdentityKey, claimsIdentity); + turnContext.StackState.Set(ChannelAdapter.BotIdentityKey, claimsIdentity); if (testCase == FlowTestCase.RootBotConsumingSkill) { // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. // This emulates a response coming to a root bot through SkillHandler. - turnContext.TurnState.Temp.SetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); + turnContext.StackState.Set(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); } if (testCase == FlowTestCase.MiddleSkill) { // Simulate the SkillConversationReference with a parent Bot ID stored in TurnState. // This emulates a response coming to a skill from another skill through SkillHandler. - turnContext.TurnState.Temp.SetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); + turnContext.StackState.Set(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs index f6ea6b5e..741cac6f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs @@ -40,7 +40,8 @@ public InterceptTurnContext(TestCloudAdapter testAdapter, ITurnContext innerTurn public IChannelAdapter Adapter => _innerTurnContext.Adapter; - public ITurnState TurnState => _innerTurnContext.TurnState; + public TurnContextStateCollection StackState => _innerTurnContext.StackState; + public TurnContextStateCollection Services => _innerTurnContext.Services; public IActivity Activity => _innerTurnContext.Activity; diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs index dcd0b944..7899a85e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs @@ -16,7 +16,7 @@ public class ProactiveBot : ActivityHandler { protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { - var claimsIdentity = turnContext.TurnState.Temp.GetValue(ChannelAdapter.BotIdentityKey) as ClaimsIdentity; + var claimsIdentity = turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) as ClaimsIdentity; var botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs index 20ff82e6..117bd6f2 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs @@ -657,7 +657,7 @@ public async Task TestDelegatingTurnContext() //turnContextMock.Setup(tc => tc.Adapter).Returns(new BotFrameworkAdapter(new SimpleCredentialProvider())); turnContextMock.Setup(tc => tc.Adapter).Returns(new NotImplementedAdapter()); - turnContextMock.Setup(tc => tc.TurnState).Returns((ITurnState) new TurnState()); + turnContextMock.Setup(tc => tc.StackState).Returns(new TurnContextStateCollection()); turnContextMock.Setup(tc => tc.Responded).Returns(false); turnContextMock.Setup(tc => tc.OnDeleteActivity(It.IsAny())); turnContextMock.Setup(tc => tc.OnSendActivities(It.IsAny())); @@ -674,7 +674,7 @@ public async Task TestDelegatingTurnContext() // Assert turnContextMock.VerifyGet(tc => tc.Activity, Times.AtLeastOnce); turnContextMock.VerifyGet(tc => tc.Adapter, Times.Once); - turnContextMock.VerifyGet(tc => tc.TurnState, Times.Once); + turnContextMock.VerifyGet(tc => tc.StackState, Times.Once); turnContextMock.VerifyGet(tc => tc.Responded, Times.Once); turnContextMock.Verify(tc => tc.OnDeleteActivity(It.IsAny()), Times.Once); turnContextMock.Verify(tc => tc.OnSendActivities(It.IsAny()), Times.Once); @@ -961,7 +961,7 @@ protected override async Task OnMessageActivityAsync(ITurnContext Task.CompletedTask); turnContext.OnSendActivities((t, a, n) => Task.FromResult(new ResourceResponse[] { new ResourceResponse() })); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs index dbe34334..02d2a398 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs @@ -150,7 +150,7 @@ public override TurnContext CreateTurnContext(IActivity activity) claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _parentBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _skillBotId)); - turnContext.TurnState.Temp.SetValue(BotIdentityKey, claimsIdentity); + turnContext.StackState.Set(BotIdentityKey, claimsIdentity); return turnContext; } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs index 0889c53f..27a581f8 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs @@ -43,7 +43,7 @@ public void Constructor_ShouldInstantiateCorrectly() public void TurnContext_ShouldBeClonedCorrectly() { var context1 = new TurnContext(new SimpleAdapter(), new Activity() { Text = "one" }); - context1.TurnState.Temp.SetValue("x", "test"); + context1.StackState.Set("x", "test"); context1.OnSendActivities((context, activities, next) => next()); context1.OnDeleteActivity((context, activity, next) => next()); context1.OnUpdateActivity((context, activity, next) => next()); @@ -51,7 +51,7 @@ public void TurnContext_ShouldBeClonedCorrectly() Assert.Equal("one", context1.Activity.Text); Assert.Equal("two", ccontext2.Activity.Text); Assert.Equal(context1.Adapter, ccontext2.Adapter); - Assert.Equal(context1.TurnState, ccontext2.TurnState); + Assert.Equal(context1.StackState, ccontext2.StackState); var binding = BindingFlags.Instance | BindingFlags.NonPublic; var onSendField = typeof(TurnContext).GetField("_onSendActivities", binding); @@ -82,14 +82,14 @@ public async Task Responded_ShouldBeTrueAfterReplyingToActivity() public void Get_ThrowsOnNullKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - Assert.Throws(() => context.TurnState.Temp.GetValue(null)); + Assert.Throws(() => context.StackState.Get(null)); } [Fact] public void Get_ShouldReturnNullOnEmptyKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - var service = context.TurnState.Temp.GetValue(string.Empty); + var service = context.StackState.Get(string.Empty); Assert.Null(service); } @@ -97,7 +97,7 @@ public void Get_ShouldReturnNullOnEmptyKey() public void Get_ShouldReturnNullWithUnknownKey() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - var result = context.TurnState.Temp.GetValue("test"); + var result = context.StackState.Get("test"); Assert.Null(result); } @@ -106,8 +106,8 @@ public void Get_ShouldReturnCachedValueUsingKeyName() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - context.TurnState.Temp.SetValue("bar", "foo"); - var result = context.TurnState.Temp.GetValue("bar"); + context.StackState.Set("bar", "foo"); + var result = context.StackState.Get("bar"); Assert.Equal("foo", result); } @@ -117,8 +117,8 @@ public void Get_ShouldReturnGenericValuegUsingTypeAsKeyName() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - context.TurnState.Temp.SetValue("foo"); - string result = context.TurnState.Temp.GetValue(); + context.StackState.Set("foo"); + string result = context.StackState.Get(); Assert.Equal("foo", result); } diff --git a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs index 58636f61..2a55cf90 100644 --- a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs @@ -77,7 +77,7 @@ public async Task TestSendAndReplyToConversationAsync(string activityType, strin // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); // Assert based on activity type, if (activityType == ActivityTypes.Message) @@ -132,7 +132,7 @@ public async Task TestCommandActivities(string commandActivityType, string name, // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); if (name.StartsWith("application/")) { // Should be sent to the channel and not to the bot. @@ -167,7 +167,7 @@ public async Task TestDeleteActivityAsync() await sut.OnDeleteActivityAsync(mockObjects.CreateTestClaims(), conversationId, activityToDelete); // Assert - Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); Assert.Equal(activityToDelete, mockObjects.ActivityIdToDelete); } @@ -186,7 +186,7 @@ public async Task TestUpdateActivityAsync() // Assert Assert.Equal("resourceId", response.Id); - Assert.NotNull(mockObjects.TurnContext.TurnState.Temp.GetValue(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); Assert.Equal(activityToUpdate, mockObjects.TurnContext.Activity.Id); Assert.Equal(activity.Text, mockObjects.UpdateActivity.Text); } @@ -308,7 +308,7 @@ private Mock CreateMockAdapter(ILogger logger) { // Create and capture the TurnContext so we can run assertions on it. TurnContext = new TurnContext(adapter.Object, conv.GetContinuationActivity()); - TurnContext.TurnState.Temp.SetValue(Client); + TurnContext.Services.Set(Client); await botCallbackHandler(TurnContext, cancel); }); diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs index 095b9f53..113e89d0 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs @@ -243,7 +243,7 @@ private class TestAdapter( ILogger logger = null, bool async = true, params BotBuilder.IMiddleware[] middlewares) - : CloudAdapter(channelServiceClientFactory, activityTaskQueue, null, logger, async, middlewares) + : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, async, middlewares) { public override Task ProcessActivityAsync(ClaimsIdentity claimsIdentity, IActivity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs rename to src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs index 621e07c1..cd2fe862 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs @@ -12,7 +12,6 @@ using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; -using Microsoft.VisualBasic; using Moq; using Xunit; @@ -381,7 +380,7 @@ public async Task State_DoNOTRememberContextState() await new TestFlow(adapter, (context, cancellationToken) => { - var obj = context.TurnState.Temp.GetValue(); + var obj = context.StackState.Get(); Assert.Null(obj); return Task.CompletedTask; }) diff --git a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs rename to src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs index 941d3b35..85215e00 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotStateSetTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs @@ -5,14 +5,12 @@ using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; -using System; -using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Microsoft.Agents.State.Tests { - public class BotStateSetTests + public class TurnStateTests { [Fact] public void TurnState_Properties() @@ -81,6 +79,7 @@ public void TurnState_TempState() Assert.Equal("botscope", turnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey)); } + /* [Fact] public async Task TurnState_DottedProperties() { @@ -177,6 +176,7 @@ public async Task TurnState_DottedProperties() Assert.Equal("Howdy", turnState.GetValue("conversation.chatHistory[1]", () => string.Empty)); } } + */ [Fact] public async Task TurnState_ReturnsDefaultForNullValueType() From d319dede8dc9f347cf13ab98852e1a11dd9e4e6a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 5 Feb 2025 10:20:31 -0600 Subject: [PATCH 14/60] Fixed merge test failures --- src/Microsoft.Agents.SDK.sln | 10 ++++++++++ .../Compat/ShowTypingMiddleware.cs | 16 +++++++++++++--- .../Microsoft.Agents.BotBuilder/TurnContext.cs | 10 ++++++++-- .../DialogExtensionsTests.cs | 2 +- .../ActivityHandlerTests.cs | 12 ++++++------ .../ChannelServiceAdapterBaseTests.cs | 8 ++++---- .../OAuthFlowTests.cs | 4 ++-- .../TurnContextTests.cs | 12 ++++++++---- .../CloudAdapterTests.cs | 6 +++--- 9 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 57030456..5d98dbfd 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -140,6 +140,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Teams", "l EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.SharePoint", "libraries\Partner\Microsoft.Agents.SharePoint\Microsoft.Agents.SharePoint.csproj", "{790020D4-42D2-40EB-87F0-9DC2A1E6C01D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{B748D33C-AC75-4AEE-9305-34D1A0126202}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{2E783725-43EE-4DF5-8379-745EEF2DCDA0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -362,6 +366,10 @@ Global {790020D4-42D2-40EB-87F0-9DC2A1E6C01D}.Debug|Any CPU.Build.0 = Debug|Any CPU {790020D4-42D2-40EB-87F0-9DC2A1E6C01D}.Release|Any CPU.ActiveCfg = Release|Any CPU {790020D4-42D2-40EB-87F0-9DC2A1E6C01D}.Release|Any CPU.Build.0 = Release|Any CPU + {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -431,6 +439,8 @@ Global {32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {261310F8-6D5E-4ECC-9250-95DDD7F3B1F1} = {32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD} {790020D4-42D2-40EB-87F0-9DC2A1E6C01D} = {32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD} + {B748D33C-AC75-4AEE-9305-34D1A0126202} = {674A812C-7287-4883-97F9-697D83750648} + {2E783725-43EE-4DF5-8379-745EEF2DCDA0} = {B748D33C-AC75-4AEE-9305-34D1A0126202} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index 1ed430a7..984c1040 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -166,15 +166,25 @@ private async Task FinishTypingTaskAsync(ITurnContext turnContext) _tasks.TryRemove(turnContext.Activity.Conversation.Id, out _); } + + private static bool IsSkillBot(ITurnContext turnContext) + { + return turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity + && BotClaims.IsBotClaim(claimIdentity.Claims); + } + /// /// Start a timer to periodically send the typing activity (bots running as skills should not send typing activity). /// /// The context object for this turn. private async Task ProcessTypingAsync(ITurnContext turnContext) { - // Override the typing background task. - await FinishTypingTaskAsync(turnContext).ConfigureAwait(false); - StartTypingTask(turnContext); + if (!IsSkillBot(turnContext) && turnContext.Activity.Type == ActivityTypes.Message) + { + // Override the typing background task. + await FinishTypingTaskAsync(turnContext).ConfigureAwait(false); + StartTypingTask(turnContext); + } } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs index 1bd27305..a6f9c7eb 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs @@ -63,8 +63,8 @@ public TurnContext(ITurnContext turnContext, IActivity activity) // all properties should be copied over except for activity. Adapter = turnContext.Adapter; - StackState = new TurnContextStateCollection(); - Services = new TurnContextStateCollection(); + StackState = turnContext.StackState; + Services = turnContext.Services; Responded = turnContext.Responded; if (turnContext is TurnContext tc) @@ -393,6 +393,12 @@ protected virtual void Dispose(bool disposing) return; } + if (disposing) + { + StackState.Dispose(); + Services.Dispose(); + } + _disposed = true; } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 50b30481..8e50218c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -143,7 +143,7 @@ public async Task RunAsyncShouldSetTelemetryClient() using (var turnContext = new TurnContext(adapter.Object, activity)) { await conversationState.LoadAsync(turnContext, false); - turnContext.StackState.Set(telemetryClientMock.Object); + turnContext.Services.Set(telemetryClientMock.Object); await DialogExtensions.RunAsync(dialog, turnContext, conversationState, CancellationToken.None); } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs index 67ea3d54..f827788f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs @@ -654,12 +654,11 @@ public async Task TestDelegatingTurnContext() // Arrange var turnContextMock = new Mock(); turnContextMock.Setup(tc => tc.Activity).Returns(new Activity { Type = ActivityTypes.Message }); - //turnContextMock.Setup(tc => tc.Adapter).Returns(new BotFrameworkAdapter(new SimpleCredentialProvider())); turnContextMock.Setup(tc => tc.Adapter).Returns(new NotImplementedAdapter()); - turnContextMock.Object.Services.Set(new Mock().Object); - turnContextMock.Setup(tc => tc.TurnState).Returns(new TurnContextStateCollection()); - turnContextMock.Object.TurnState.Add(new Mock().Object); + turnContextMock.Setup(tc => tc.StackState).Returns(new TurnContextStateCollection()); + turnContextMock.Setup(tc => tc.Services).Returns(new TurnContextStateCollection()); + turnContextMock.Object.Services.Set(new Mock().Object); turnContextMock.Setup(tc => tc.Responded).Returns(false); turnContextMock.Setup(tc => tc.OnDeleteActivity(It.IsAny())); turnContextMock.Setup(tc => tc.OnSendActivities(It.IsAny())); @@ -680,7 +679,8 @@ public async Task TestDelegatingTurnContext() // Assert turnContextMock.VerifyGet(tc => tc.Activity, Times.AtLeastOnce); turnContextMock.VerifyGet(tc => tc.Adapter, Times.Once); - turnContextMock.VerifyGet(tc => tc.TurnState, Times.Exactly(2)); + turnContextMock.VerifyGet(tc => tc.StackState, Times.Exactly(1)); + turnContextMock.VerifyGet(tc => tc.Services, Times.Exactly(1)); turnContextMock.VerifyGet(tc => tc.Responded, Times.Once); turnContextMock.Verify(tc => tc.OnDeleteActivity(It.IsAny()), Times.Once); turnContextMock.Verify(tc => tc.OnSendActivities(It.IsAny()), Times.Once); @@ -693,7 +693,7 @@ public async Task TestDelegatingTurnContext() turnContextMock.Verify(tc => tc.TraceActivityAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); //Validate entities inheritated and managed by TypedTurnContext - Assert.NotNull(typedTurnContext.Connector); + Assert.NotNull(typedTurnContext.Services.Get()); Assert.NotNull(typedTurnContext.Activity); } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs index 0b1af068..6cfd2d9c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs @@ -38,7 +38,7 @@ public async Task UpdateActivityAsync_ShouldReturnUpdatedResource() var adapter = new TestChannelAdapter(new Mock().Object); var context = new TurnContext(adapter, new Activity()); - context.StackState.Set(CreateMockConnectorClient().Object); + context.Services.Set(CreateMockConnectorClient().Object); //Act var result = await context.Adapter.UpdateActivityAsync(context, new Activity(), default(CancellationToken)); @@ -76,7 +76,7 @@ public async Task DeleteActivityAsync_ShouldCompleteTask() var connectorClient = CreateMockConnectorClient(); var adapter = new TestChannelAdapter(new Mock().Object); var context = new TurnContext(adapter, new Activity()); - context.StackState.Set(connectorClient.Object); + context.Services.Set(connectorClient.Object); //Act await context.Adapter.DeleteActivityAsync(context, _reference, default(CancellationToken)); @@ -295,7 +295,7 @@ public async Task SendActivitiesAsync_ShouldReplyActivity() var connectorClient = CreateMockConnectorClient(); var adapter = new TestChannelAdapter(new Mock().Object); var context = new TurnContext(adapter, new Activity()); - context.StackState.Set(connectorClient.Object); + context.Services.Set(connectorClient.Object); var activities = new Activity[] { new Activity(type: ActivityTypes.Message, value: "reply activity", replyToId: "replyToId") @@ -317,7 +317,7 @@ public async Task SendActivitiesAsync_ShouldSendActivity() var connectorClient = CreateMockConnectorClient(); var adapter = new TestChannelAdapter(new Mock().Object); var context = new TurnContext(adapter, new Activity()); - context.StackState.Set(connectorClient.Object); + context.Services.Set(connectorClient.Object); var activities = new Activity[] { new Activity(type: ActivityTypes.Message, value: "message activity") diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs index d64ee5a0..28be2312 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs @@ -46,7 +46,7 @@ public async Task SignOutUserAsync_ShouldLogOutSuccessfully() mockUserTokenClient.Setup( x => x.SignOutUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - context.TurnState.Add(mockUserTokenClient.Object); + context.Services.Set(mockUserTokenClient.Object); //Act await _flow.SignOutUserAsync(context); @@ -82,7 +82,7 @@ void ValidateResponses(IActivity[] activities) x => x.GetUserTokenAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TokenResponse)null); - context.TurnState.Add(mockUserTokenClient.Object); + context.Services.Set(mockUserTokenClient.Object); //Act var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1)); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs index 67387c2f..ff6536f7 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/TurnContextTests.cs @@ -588,10 +588,12 @@ public void TraceActivityAsync_ShouldNotUpdateContextResponseOnMultipleDispose() var activity = TestMessage.Message(); // Create a custom disposable TurnContextStateCollection - var turnState = new CustomDisposableTurnContextStateCollection(); + var stackState = new CustomDisposableTurnContextStateCollection(); + var services = new CustomDisposableTurnContextStateCollection(); var mockTurnContext = new Mock(); mockTurnContext.Setup(tc => tc.Adapter).Returns(adapter); - mockTurnContext.Setup(tc => tc.TurnState).Returns(turnState); + mockTurnContext.Setup(tc => tc.StackState).Returns(stackState); + mockTurnContext.Setup(tc => tc.Services).Returns(services); var context = new TurnContext(mockTurnContext.Object, activity); @@ -600,8 +602,10 @@ public void TraceActivityAsync_ShouldNotUpdateContextResponseOnMultipleDispose() context.Dispose(); // Assert - Assert.True(turnState.IsDisposed, "TurnState.Dispose was not called."); - Assert.Equal(1, turnState.DisposeCallCount); + Assert.True(stackState.IsDisposed, "StackState.Dispose was not called."); + Assert.Equal(1, stackState.DisposeCallCount); + Assert.True(services.IsDisposed, "Services.Dispose was not called."); + Assert.Equal(1, services.DisposeCallCount); } private class CustomDisposableTurnContextStateCollection : TurnContextStateCollection, IDisposable diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs index 113e89d0..19ad7929 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs @@ -221,7 +221,7 @@ private static Record UseRecord() var logger = new Mock>(); var middleware = new Mock(); - var adapter = new TestAdapter(factory.Object, queue.Object, logger.Object, true, middleware.Object); + var adapter = new TestAdapter(factory.Object, queue.Object, logger.Object, new AdapterOptions() { Async = true }, middleware.Object); return new(adapter, factory, queue, logger); } @@ -241,9 +241,9 @@ private class TestAdapter( IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, ILogger logger = null, - bool async = true, + AdapterOptions options = null, params BotBuilder.IMiddleware[] middlewares) - : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, async, middlewares) + : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, options, middlewares) { public override Task ProcessActivityAsync(ClaimsIdentity claimsIdentity, IActivity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { From 603a6eda02220d6cefbc6a267affef95c52ec7d2 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 5 Feb 2025 10:39:30 -0600 Subject: [PATCH 15/60] Minor Application EchoBot cleanup --- .../messaging.echoBot/EchoBotApplication.cs | 11 ++++++----- .../Application/messaging.echoBot/StateExtensions.cs | 11 ++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index d918de40..c36e85b6 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -1,4 +1,7 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; @@ -11,7 +14,7 @@ public class EchoBotApplication : Application { public EchoBotApplication(ApplicationOptions options) : base(options) { - OnConversationUpdate("membersAdded", WelcomeMessageAsync); + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); // Listen for user to say "/reset" and then delete conversation state OnMessage("/reset", DeleteStateHandlerAsync); @@ -48,10 +51,8 @@ public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, ITurn /// public static async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { - int count = turnState.Conversation.MessageCount(); - // Increment count state. - turnState.Conversation.MessageCount(++count); + int count = turnState.Conversation.IncrementMessageCount(); await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); } diff --git a/src/samples/Application/messaging.echoBot/StateExtensions.cs b/src/samples/Application/messaging.echoBot/StateExtensions.cs index c46e0c4c..04762db4 100644 --- a/src/samples/Application/messaging.echoBot/StateExtensions.cs +++ b/src/samples/Application/messaging.echoBot/StateExtensions.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Agents.BotBuilder.State; namespace EchoBot @@ -14,5 +16,12 @@ public static void MessageCount(this ConversationState state, int value) { state.SetValue("countKey", value); } + + public static int IncrementMessageCount(this ConversationState state) + { + var count = state.GetValue("countKey"); + state.SetValue("countKey", ++count); + return count; + } } } From 81c0783e0ff0445d1e0b0716a2f5e2fc2f7a608d Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 7 Feb 2025 10:17:02 -0600 Subject: [PATCH 16/60] Correct deserializtion of Dictionary value types --- .../Converters/DictionaryOfObjectConverter.cs | 20 +++++++++++++++++++ .../Serialization/TypeExtensions.cs | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs index 2365525a..3e6be407 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs @@ -38,6 +38,8 @@ internal class DictionaryOfObjectConverter : JsonConverter dict) { + dict.RemoveTypeInfo(); + foreach (KeyValuePair child in dict) { var childObj = child.Value; @@ -55,6 +57,24 @@ internal class DictionaryOfObjectConverter : JsonConverter dict) + { + dict.Remove("$type"); + dict.Remove("$typeAssembly"); + } } } From 9a1bc0c412cf5a56dc5fb0bb22329620594d8e6a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 7 Feb 2025 10:18:06 -0600 Subject: [PATCH 17/60] Corrected IStatePropertyAccessor.GetValue for back-compat --- .../State/BotState.cs | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs index 54bf444a..d1b116d2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs @@ -477,34 +477,11 @@ public async Task DeleteAsync(ITurnContext turnContext, CancellationToken cancel /// A representing the asynchronous operation. public async Task GetAsync(ITurnContext turnContext, Func defaultValueFactory, CancellationToken cancellationToken) { - T result = default(T); - await _botState.LoadAsync(turnContext, false, cancellationToken).ConfigureAwait(false); - try - { - // if T is a value type, lookup up will throw key not found if not found, but as perf - // optimization it will return null if not found for types which are not value types (string and object). - result = _botState.GetValue(Name, defaultValueFactory); - - if (result == null && defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - catch (KeyNotFoundException) - { - if (defaultValueFactory != null) - { - // use default Value Factory and save default value for any further calls - result = defaultValueFactory(); - await SetAsync(turnContext, result, cancellationToken).ConfigureAwait(false); - } - } - - return result; + // if T is a value type, lookup up will throw key not found if not found, but as perf + // optimization it will return null if not found for types which are not value types (string and object). + return _botState.GetValue(Name, defaultValueFactory); } /// From 815b2866dce4d731f42df5a3c9affc299bc7e23a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 7 Feb 2025 17:10:54 -0600 Subject: [PATCH 18/60] Rename extensions to Microsoft.Agents.Extensions.* --- src/Microsoft.Agents.SDK.sln | 8 +-- .../App/AdaptiveCards/AdaptiveCardsFeature.cs | 1 - .../App/Application.cs | 2 - .../App/TypingTimer.cs | 13 +++-- .../Compat/SharePointActivityHandler.cs | 4 +- .../SharePointSSOTokenExchangeMiddleware.cs | 6 +-- ...osoft.Agents.Extensions.SharePoint.csproj} | 2 +- .../Models/AceData.cs | 2 +- .../Models/AceRequest.cs | 2 +- .../Models/Actions/BaseAction.cs | 2 +- .../Models/Actions/ConfirmationDialog.cs | 2 +- .../Models/Actions/ExecuteAction.cs | 2 +- .../Models/Actions/ExternalLinkAction.cs | 2 +- .../Actions/ExternalLinkActionParameters.cs | 2 +- .../Models/Actions/FocusParameters.cs | 2 +- .../Models/Actions/GetLocationAction.cs | 2 +- .../Actions/GetLocationActionParameters.cs | 2 +- .../Models/Actions/IAction.cs | 2 +- .../Models/Actions/ICardActionParameters.cs | 2 +- .../Models/Actions/IOnCardSelectionAction.cs | 2 +- .../Models/Actions/Location.cs | 2 +- .../Models/Actions/QuickViewAction.cs | 2 +- .../Actions/QuickViewActionParameters.cs | 2 +- .../Models/Actions/SelectMediaAction.cs | 2 +- .../Actions/SelectMediaActionParameters.cs | 2 +- .../Models/Actions/ShowLocationAction.cs | 2 +- .../Actions/ShowLocationActionParameters.cs | 2 +- .../Models/Actions/SubmitAction.cs | 2 +- .../Models/BaseHandleActionResponse.cs | 2 +- .../Models/CardView/BaseCardComponent.cs | 2 +- .../Models/CardView/CardBarComponent.cs | 2 +- .../Models/CardView/CardButtonComponent.cs | 4 +- .../Models/CardView/CardComponentName.cs | 2 +- .../Models/CardView/CardImage.cs | 2 +- .../Models/CardView/CardSearchBoxButton.cs | 4 +- .../Models/CardView/CardSearchBoxComponent.cs | 2 +- .../CardView/CardSearchFooterComponent.cs | 4 +- .../Models/CardView/CardTextComponent.cs | 2 +- .../CardView/CardTextInputBaseButton.cs | 4 +- .../Models/CardView/CardTextInputComponent.cs | 2 +- .../CardView/CardTextInputIconButton.cs | 2 +- .../CardView/CardTextInputTitleButton.cs | 2 +- .../Models/CardView/CardViewParameters.cs | 2 +- .../Models/CardView/ICardButtonBase.cs | 4 +- .../Models/CardViewHandleActionResponse.cs | 2 +- .../Models/CardViewResponse.cs | 6 +-- .../GetPropertyPaneConfigurationResponse.cs | 2 +- .../Models/IPropertyPaneFieldProperties.cs | 2 +- .../IPropertyPaneGroupOrConditionalGroup.cs | 2 +- .../Models/NoOpHandleActionResponse.cs | 2 +- .../Models/PropertyPaneCheckboxProperties.cs | 2 +- .../PropertyPaneChoiceGroupIconProperties.cs | 2 +- .../PropertyPaneChoiceGroupImageSize.cs | 2 +- .../Models/PropertyPaneChoiceGroupOption.cs | 2 +- .../PropertyPaneChoiceGroupProperties.cs | 2 +- .../Models/PropertyPaneDropDownOption.cs | 2 +- .../Models/PropertyPaneDropDownProperties.cs | 2 +- .../Models/PropertyPaneGroup.cs | 2 +- .../Models/PropertyPaneGroupField.cs | 2 +- .../Models/PropertyPaneLabelProperties.cs | 2 +- .../PropertyPaneLinkPopupWindowProperties.cs | 2 +- .../Models/PropertyPaneLinkProperties.cs | 2 +- .../Models/PropertyPanePage.cs | 2 +- .../Models/PropertyPanePageHeader.cs | 2 +- .../Models/PropertyPaneSliderProperties.cs | 2 +- .../Models/PropertyPaneTextFieldProperties.cs | 2 +- .../Models/PropertyPaneToggleProperties.cs | 2 +- .../Models/QuickViewData.cs | 2 +- .../Models/QuickViewHandleActionResponse.cs | 2 +- .../Models/QuickViewResponse.cs | 4 +- .../README.md | 2 +- .../Converters/AceDataConverter.cs | 4 +- .../Converters/AceRequestConverter.cs | 4 +- .../Serialization/SerializationInit.cs | 2 +- .../Serialization/SerializerExtensions.cs | 4 +- .../App/ConfigHandlerAsync.cs | 9 ++-- .../App/FeedbackLoopData.cs | 6 ++- .../App/FeedbackLoopHandler.cs | 7 ++- .../App/FileConsentCardHandler.cs | 9 ++-- .../App/IInputFileDownloader.cs | 6 ++- .../App/InputFile.cs | 6 ++- .../App/Meetings/MeetingsFeature.cs | 9 ++-- .../App/Meetings/MeetingsHandlers.cs | 9 ++-- .../MessageExtensionsFeature.cs | 9 ++-- .../MessageExtensionsHandlers.cs | 9 ++-- .../App/O365ConnectorCardActionHandler.cs | 9 ++-- .../App/ReadReceiptHandler.cs | 9 ++-- .../App/TaskModules/TaskModulesFeature.cs | 9 ++-- .../App/TaskModules/TaskModulesHandlers.cs | 9 ++-- .../App/TaskModules/TaskModulesOptions.cs | 5 +- .../App/TeamsApplication.cs | 15 +++--- .../App/TeamsConversationUpdateEvents.cs | 7 ++- .../AssemblyInfo.cs | 2 +- .../AttachmentExtensions.cs | 4 +- .../Compat/TeamsActivityHandler.cs | 6 +-- .../Compat/TeamsSSOTokenExchangeMiddleware.cs | 2 +- .../Connector/ITeamsConnectorClient.cs | 2 +- .../Connector/ITeamsOperations.cs | 4 +- .../Connector/RestTeamsConnectorClient.cs | 2 +- .../Connector/RestTeamsOperations.cs | 4 +- .../Connector/RetryAction.cs | 2 +- .../Connector/RetryParams.cs | 2 +- .../Connector/TeamsInfo.cs | 4 +- .../Connector/ThrottleException.cs | 2 +- .../Connector/TimeSpanExtensions.cs | 2 +- .../Microsoft.Agents.Extensions.Teams.csproj} | 2 +- .../Models/AppBasedLinkQuery.cs | 2 +- .../Models/BatchFailedEntriesResponse.cs | 2 +- .../Models/BatchFailedEntry.cs | 2 +- .../Models/BatchOperationState.cs | 2 +- .../Models/BotConfigAuth.cs | 2 +- .../Models/CacheInfo.cs | 2 +- .../Models/ChannelInfo.cs | 2 +- .../Models/ConfigAuthResponse.cs | 2 +- .../Models/ConfigResponse.cs | 2 +- .../Models/ConfigResponseBase.cs | 2 +- .../Models/ConfigTaskResponse.cs | 2 +- .../Models/ContentType.cs | 2 +- .../Models/ConversationList.cs | 2 +- .../Models/FileConsentCard.cs | 2 +- .../Models/FileConsentCardResponse.cs | 2 +- .../Models/FileDownloadInfo.cs | 2 +- .../Models/FileInfoCard.cs | 2 +- .../Models/FileUploadInfo.cs | 2 +- .../Models/MeetingDetails.cs | 2 +- .../Models/MeetingDetailsBase.cs | 2 +- .../Models/MeetingEndEventDetails.cs | 2 +- .../Models/MeetingEventDetails.cs | 2 +- .../Models/MeetingInfo.cs | 2 +- .../Models/MeetingNotification.cs | 2 +- .../Models/MeetingNotificationBase.cs | 2 +- .../Models/MeetingNotificationChannelData.cs | 2 +- ...MeetingNotificationRecipientFailureInfo.cs | 2 +- .../Models/MeetingNotificationResponse.cs | 2 +- .../Models/MeetingParticipantInfo.cs | 2 +- .../Models/MeetingParticipantsEventDetails.cs | 2 +- .../Models/MeetingStageSurface.cs | 2 +- .../Models/MeetingStartEventDetails.cs | 2 +- .../Models/MeetingTabIconSurface.cs | 2 +- .../Models/MessageActionsPayload.cs | 2 +- .../Models/MessageActionsPayloadApp.cs | 2 +- .../Models/MessageActionsPayloadAttachment.cs | 2 +- .../Models/MessageActionsPayloadBody.cs | 2 +- .../MessageActionsPayloadConversation.cs | 2 +- .../Models/MessageActionsPayloadFrom.cs | 2 +- .../Models/MessageActionsPayloadMention.cs | 2 +- .../Models/MessageActionsPayloadReaction.cs | 2 +- .../Models/MessageActionsPayloadUser.cs | 2 +- .../Models/MessagingExtensionAction.cs | 2 +- .../MessagingExtensionActionResponse.cs | 2 +- .../Models/MessagingExtensionAttachment.cs | 2 +- .../Models/MessagingExtensionParameter.cs | 2 +- .../Models/MessagingExtensionQuery.cs | 2 +- .../Models/MessagingExtensionQueryOptions.cs | 2 +- .../Models/MessagingExtensionResponse.cs | 2 +- .../Models/MessagingExtensionResult.cs | 2 +- .../MessagingExtensionSuggestedAction.cs | 2 +- .../Models/NotificationInfo.cs | 2 +- .../Models/O365ConnectorCard.cs | 2 +- .../Models/O365ConnectorCardActionBase.cs | 2 +- .../Models/O365ConnectorCardActionCard.cs | 2 +- .../Models/O365ConnectorCardActionQuery.cs | 2 +- .../Models/O365ConnectorCardDateInput.cs | 2 +- .../Models/O365ConnectorCardFact.cs | 2 +- .../Models/O365ConnectorCardHttpPOST.cs | 2 +- .../Models/O365ConnectorCardImage.cs | 2 +- .../Models/O365ConnectorCardInputBase.cs | 2 +- .../O365ConnectorCardMultichoiceInput.cs | 2 +- ...O365ConnectorCardMultichoiceInputChoice.cs | 2 +- .../Models/O365ConnectorCardOpenUri.cs | 2 +- .../Models/O365ConnectorCardOpenUriTarget.cs | 2 +- .../Models/O365ConnectorCardSection.cs | 2 +- .../Models/O365ConnectorCardTextInput.cs | 2 +- .../Models/O365ConnectorCardViewAction.cs | 2 +- .../Models/OnBehalfOf.cs | 2 +- .../Models/ReadReceiptInfo.cs | 2 +- .../Models/SigninStateVerificationQuery.cs | 2 +- .../Models/Surface.cs | 2 +- .../Models/SurfaceType.cs | 2 +- .../Models/TabContext.cs | 2 +- .../Models/TabEntityContext.cs | 2 +- .../Models/TabRequest.cs | 2 +- .../Models/TabResponse.cs | 2 +- .../Models/TabResponseCard.cs | 2 +- .../Models/TabResponseCards.cs | 2 +- .../Models/TabResponsePayload.cs | 2 +- .../Models/TabSubmit.cs | 2 +- .../Models/TabSubmitData.cs | 2 +- .../Models/TabSuggestedActions.cs | 2 +- .../Models/TargetedMeetingNotification.cs | 2 +- .../TargetedMeetingNotificationValue.cs | 2 +- .../Models/TaskModuleAction.cs | 2 +- .../Models/TaskModuleCardResponse.cs | 2 +- .../Models/TaskModuleContinueResponse.cs | 2 +- .../Models/TaskModuleMessageResponse.cs | 2 +- .../Models/TaskModuleRequest.cs | 2 +- .../Models/TaskModuleRequestContext.cs | 2 +- .../Models/TaskModuleResponse.cs | 2 +- .../Models/TaskModuleResponseBase.cs | 2 +- .../Models/TaskModuleTaskInfo.cs | 2 +- .../Models/TeamDetails.cs | 2 +- .../Models/TeamInfo.cs | 2 +- .../Models/TeamMember.cs | 2 +- .../Models/TeamsChannelAccount.cs | 2 +- .../Models/TeamsChannelData.cs | 2 +- .../Models/TeamsChannelDataSettings.cs | 2 +- .../Models/TeamsMeetingInfo.cs | 2 +- .../Models/TeamsMeetingMember.cs | 2 +- .../Models/TeamsMeetingParticipant.cs | 2 +- .../Models/TeamsPagedMembersResult.cs | 2 +- .../Models/TeamsParticipantChannelAccount.cs | 2 +- .../Models/TenantInfo.cs | 2 +- .../Models/UserMeetingDetails.cs | 2 +- .../README.md | 2 +- ...ssagingExtensionActionResponseConverter.cs | 4 +- .../MessagingExtensionAttachmentConverter.cs | 4 +- .../Converters/SurfaceConverter.cs | 15 ++++-- .../Converters/TabSubmitDataConverter.cs | 4 +- .../TaskModuleCardResponseConverter.cs | 4 +- .../TaskModuleContinueResponseConverter.cs | 4 +- .../TaskModuleMessageResponseConverter.cs | 4 +- .../TaskModuleResponseBaseConverter.cs | 4 +- .../Converters/TaskModuleResponseConverter.cs | 4 +- .../Converters/TeamsChannelDataConverter.cs | 4 +- .../Serialization/SerializationInit.cs | 2 +- .../Serialization/SerializerExtensions.cs | 4 +- .../TeamsActivityExtensions.cs | 4 +- .../TeamsChannelServiceClientFactory.cs | 4 +- .../AspNetCore/ServiceCollectionExtensions.cs | 6 +-- .../messaging.echoBot/EchoBotApplication.cs | 2 + .../Application/messaging.echoBot/Program.cs | 25 +++------ .../AuthenticationBot.csproj | 2 +- src/samples/AuthenticationBot/Program.cs | 2 +- .../AdaptiveCardActions.csproj | 2 +- .../Bots/AdaptiveCardActionsBot.cs | 2 +- .../Teams/AdaptiveCardActions/Program.cs | 2 +- .../appManifest/manifest.json | 2 +- .../Bots/TeamsConversationBot.cs | 6 +-- .../ConversationBot/ConversationBot.csproj | 2 +- src/samples/Teams/ConversationBot/Program.cs | 2 +- .../ConversationBot/appManifest/manifest.json | 2 +- .../LinkUnfurling/AppManifest/manifest.json | 2 +- .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 4 +- .../Teams/LinkUnfurling/LinkUnfurling.csproj | 2 +- src/samples/Teams/LinkUnfurling/Program.cs | 2 +- .../Bots/MeetingContextBot.cs | 6 +-- .../MeetingContextApp.csproj | 2 +- .../Bots/InMeetingNotifications.cs | 8 +-- .../InMeetingNotificationsBot.csproj | 2 +- .../Teams/Meetings-Notification/Program.cs | 2 +- .../Bots/TeamsMessagingExtensionsSearchBot.cs | 4 +- .../MessagingExtensionsSearch.csproj | 2 +- .../MessagingExtensionsSearch/Program.cs | 2 +- .../appManifest/manifest.json | 2 +- .../TaskModule/Bots/TeamsTaskModuleBot.cs | 4 +- .../Models/TaskModuleResponseFactory.cs | 2 +- src/samples/Teams/TaskModule/Program.cs | 2 +- .../Teams/TaskModule/TaskModule.csproj | 2 +- .../TaskModule/appManifest/manifest.json | 2 +- .../bot-all-cards/appManifest/manifest.json | 2 +- .../BotConversationSsoQuickstart.csproj | 2 +- .../Bots/DialogBot.cs | 2 +- .../Program.cs | 4 +- .../appManifest/manifest.json | 2 +- .../Bots/ActivityBot.cs | 2 +- .../PeoplePicker.csproj | 2 +- .../Program.cs | 2 +- .../appManifest/manifest.json | 2 +- .../Bots/ActivityBot.cs | 2 +- .../Program.cs | 2 +- .../ReceiveMessagesWithRSC.csproj | 2 +- .../appManifest/manifest.json | 2 +- .../BotRequestApproval.csproj | 2 +- .../bot-request-approval/Bots/ActivityBot.cs | 6 +-- .../Teams/bot-tag-mention/Bots/DialogBot.cs | 2 +- .../bot-tag-mention/Dialogs/MainDialog.cs | 6 +-- src/samples/Teams/bot-tag-mention/Program.cs | 4 +- .../bot-tag-mention/TagMentionBot.csproj | 2 +- .../AppManifest/manifest.json | 2 +- .../Bots/DialogBot.cs | 2 +- .../Teams/bot-teams-authentication/Program.cs | 2 +- .../bot-teams-authentication/TeamsAuth.csproj | 2 +- .../Bots/ActivityBot.cs | 4 +- .../TypeaheadSearch.csproj | 2 +- .../Microsoft.Agents.BotBuilder.Tests.csproj | 2 +- .../SharePointActivityHandlerTests.cs | 6 +-- .../AppBasedLinkQueryTests.cs | 4 +- .../AttachmentExtensionsTests.cs | 4 +- .../BotConfigAuthTests.cs | 4 +- .../CacheInfoTests.cs | 4 +- .../ChannelInfoTests.cs | 4 +- .../ConfigAuthResponseTests.cs | 4 +- .../ConfigResponseTests.cs | 4 +- .../ConfigTaskResponseTests.cs | 4 +- .../Connector/RestTeamsOperationsTests.cs | 6 +-- .../Connector/RetryActionTests.cs | 4 +- .../Connector/RetryParamTests.cs | 4 +- .../ConversationListTests.cs | 4 +- .../FileConsentCardResponseTests.cs | 4 +- .../FileConsentCardTests.cs | 4 +- .../FileDownloadInfoTests.cs | 4 +- .../FileInfoCardTests.cs | 4 +- .../FileUploadInfoTests.cs | 4 +- .../MeetingParticipantInfoTests.cs | 4 +- .../MeetingParticipantsEventDetailsTests.cs | 4 +- .../MessageActionsPayloadAppTests.cs | 4 +- .../MessageActionsPayloadAttachmentTests.cs | 4 +- .../MessageActionsPayloadBodyTests.cs | 4 +- ...MessageActionsPayloadConversationsTests.cs | 4 +- .../MessageActionsPayloadFromTests.cs | 4 +- .../MessageActionsPayloadMentionTests.cs | 4 +- .../MessageActionsPayloadReactionTests.cs | 4 +- .../MessageActionsPayloadTests.cs | 4 +- .../MessageActionsPayloadUserTests.cs | 4 +- .../MessagingExtensionActionResponseTests.cs | 4 +- .../MessagingExtensionActionTests.cs | 4 +- .../MessagingExtensionAttachmentTests.cs | 4 +- .../MessagingExtensionParametersTests.cs | 4 +- .../MessagingExtensionQueryOptionsTests.cs | 4 +- .../MessagingExtensionQueryTests.cs | 4 +- .../MessagingExtensionResponseTests.cs | 4 +- .../MessagingExtensionResultTests.cs | 4 +- .../MessagingExtensionSuggestedActionTests.cs | 4 +- ...soft.Agents.Extensions.Teams.Tests.csproj} | 3 +- .../NotImplementedAdapter.cs | 2 +- .../NotificationInfoTests.cs | 4 +- .../O365ConnectorCardActionBaseTests.cs | 4 +- .../O365ConnectorCardActionCardTests.cs | 4 +- .../O365ConnectorCardActionQueryTests.cs | 4 +- .../O365ConnectorCardDateInputTests.cs | 4 +- .../O365ConnectorCardFactTests.cs | 4 +- .../O365ConnectorCardHttpPOSTTests.cs | 4 +- .../O365ConnectorCardImageTests.cs | 4 +- .../O365ConnectorCardInputBaseTests.cs | 4 +- ...onnectorCardMultichoiceInputChoiceTests.cs | 4 +- .../O365ConnectorCardMultichoiceInputTests.cs | 4 +- .../O365ConnectorCardOpenUriTargetTests.cs | 4 +- .../O365ConnectorCardOpenUriTests.cs | 4 +- .../O365ConnectorCardSectionTests.cs | 4 +- .../O365ConnectorCardTests.cs | 4 +- .../O365ConnectorCardTextInputTests.cs | 4 +- .../O365ConnectorCardViewActionTests.cs | 4 +- .../ReadReceiptInfoTests.cs | 4 +- .../SigninStateVerificationQueryTests.cs | 4 +- .../SimpleAdapter.cs | 2 +- .../SurfaceTests.cs | 53 +++++++++++++++++++ .../TabContextTests.cs | 4 +- .../TabEntityContextTests.cs | 4 +- .../TabRequestTests.cs | 6 +-- .../TabResponseCardTests.cs | 4 +- .../TabResponseCardsTests.cs | 6 +-- .../TabResponsePayloadTests.cs | 6 +-- .../TabResponseTests.cs | 6 +-- .../TabSubmitDataTests.cs | 6 +-- .../TabSubmitTests.cs | 6 +-- .../TabSuggestedActionsTests.cs | 6 +-- .../TabsTestData.cs | 4 +- .../TaskModuleActionTests.cs | 4 +- .../TaskModuleCardResponseTests.cs | 4 +- .../TaskModuleContinueResponseTests.cs | 4 +- .../TaskModuleMessageResponseTests.cs | 4 +- .../TaskModuleRequestContextTests.cs | 4 +- .../TaskModuleRequestTests.cs | 4 +- .../TaskModuleResponseBaseTests.cs | 4 +- .../TaskModuleResponseTests.cs | 4 +- .../TaskModuleTaskInfoTests.cs | 4 +- .../TeamDetailsTests.cs | 4 +- .../TeamInfoTests.cs | 4 +- .../TeamsActivityHandlerTests.cs | 4 +- .../TeamsChannelAccountTests.cs | 4 +- .../TeamsChannelDataTests.cs | 28 ++++++++-- .../TeamsMeetingInfoTests.cs | 4 +- .../TeamsMeetingParticipantTests.cs | 4 +- .../TeamsPagedMembersResultTests.cs | 4 +- .../TeamsParticipantChannelAccountTests.cs | 4 +- .../TenantInfoTests.cs | 4 +- .../TestActivityHandler.cs | 6 +-- .../ServiceCollectionExtensionsTests.cs | 2 +- 378 files changed, 705 insertions(+), 585 deletions(-) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Compat/SharePointActivityHandler.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Compat/SharePointSSOTokenExchangeMiddleware.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj => Extensions/Microsoft.Agents.Extensions.SharePoint/Microsoft.Agents.Extensions.SharePoint.csproj} (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/AceData.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/AceRequest.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/BaseAction.cs (89%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ConfirmationDialog.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ExecuteAction.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ExternalLinkAction.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ExternalLinkActionParameters.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/FocusParameters.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/GetLocationAction.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/GetLocationActionParameters.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/IAction.cs (85%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ICardActionParameters.cs (86%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/IOnCardSelectionAction.cs (86%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/Location.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/QuickViewAction.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/QuickViewActionParameters.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/SelectMediaAction.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/SelectMediaActionParameters.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ShowLocationAction.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/ShowLocationActionParameters.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/Actions/SubmitAction.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/BaseHandleActionResponse.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/BaseCardComponent.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardBarComponent.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardButtonComponent.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardComponentName.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardImage.cs (90%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardSearchBoxButton.cs (82%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardSearchBoxComponent.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardSearchFooterComponent.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardTextComponent.cs (91%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardTextInputBaseButton.cs (83%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardTextInputComponent.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardTextInputIconButton.cs (87%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardTextInputTitleButton.cs (87%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/CardViewParameters.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardView/ICardButtonBase.cs (83%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardViewHandleActionResponse.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/CardViewResponse.cs (88%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/GetPropertyPaneConfigurationResponse.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/IPropertyPaneFieldProperties.cs (88%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/IPropertyPaneGroupOrConditionalGroup.cs (88%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/NoOpHandleActionResponse.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneCheckboxProperties.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneChoiceGroupIconProperties.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneChoiceGroupImageSize.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneChoiceGroupOption.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneChoiceGroupProperties.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneDropDownOption.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneDropDownProperties.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneGroup.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneGroupField.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneLabelProperties.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneLinkPopupWindowProperties.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneLinkProperties.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPanePage.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPanePageHeader.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneSliderProperties.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneTextFieldProperties.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/PropertyPaneToggleProperties.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/QuickViewData.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/QuickViewHandleActionResponse.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Models/QuickViewResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/README.md (55%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Serialization/Converters/AceDataConverter.cs (84%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Serialization/Converters/AceRequestConverter.cs (85%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Serialization/SerializationInit.cs (85%) rename src/libraries/{Partner/Microsoft.Agents.SharePoint => Extensions/Microsoft.Agents.Extensions.SharePoint}/Serialization/SerializerExtensions.cs (77%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/ConfigHandlerAsync.cs (78%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/FeedbackLoopData.cs (87%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/FeedbackLoopHandler.cs (82%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/FileConsentCardHandler.cs (80%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/IInputFileDownloader.cs (85%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/InputFile.cs (87%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/Meetings/MeetingsFeature.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/Meetings/MeetingsHandlers.cs (91%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/MessageExtensions/MessageExtensionsFeature.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/MessageExtensions/MessageExtensionsHandlers.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/O365ConnectorCardActionHandler.cs (79%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/ReadReceiptHandler.cs (76%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/TaskModules/TaskModulesFeature.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/TaskModules/TaskModulesHandlers.cs (86%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/TaskModules/TaskModulesOptions.cs (77%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/TeamsApplication.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/App/TeamsConversationUpdateEvents.cs (90%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/AssemblyInfo.cs (62%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/AttachmentExtensions.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Compat/TeamsActivityHandler.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Compat/TeamsSSOTokenExchangeMiddleware.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/ITeamsConnectorClient.cs (91%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/ITeamsOperations.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/RestTeamsConnectorClient.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/RestTeamsOperations.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/RetryAction.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/RetryParams.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/TeamsInfo.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/ThrottleException.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Connector/TimeSpanExtensions.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj => Extensions/Microsoft.Agents.Extensions.Teams/Microsoft.Agents.Extensions.Teams.csproj} (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/AppBasedLinkQuery.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/BatchFailedEntriesResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/BatchFailedEntry.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/BatchOperationState.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/BotConfigAuth.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/CacheInfo.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ChannelInfo.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ConfigAuthResponse.cs (89%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ConfigResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ConfigResponseBase.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ConfigTaskResponse.cs (90%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ContentType.cs (90%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ConversationList.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/FileConsentCard.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/FileConsentCardResponse.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/FileDownloadInfo.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/FileInfoCard.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/FileUploadInfo.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingDetails.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingDetailsBase.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingEndEventDetails.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingEventDetails.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingInfo.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingNotification.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingNotificationBase.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingNotificationChannelData.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingNotificationRecipientFailureInfo.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingNotificationResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingParticipantInfo.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingParticipantsEventDetails.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingStageSurface.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingStartEventDetails.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MeetingTabIconSurface.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayload.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadApp.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadAttachment.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadBody.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadConversation.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadFrom.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadMention.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadReaction.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessageActionsPayloadUser.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionAction.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionActionResponse.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionAttachment.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionParameter.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionQuery.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionQueryOptions.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionResponse.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionResult.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/MessagingExtensionSuggestedAction.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/NotificationInfo.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCard.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardActionBase.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardActionCard.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardActionQuery.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardDateInput.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardFact.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardHttpPOST.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardImage.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardInputBase.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardMultichoiceInput.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardMultichoiceInputChoice.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardOpenUri.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardOpenUriTarget.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardSection.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardTextInput.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/O365ConnectorCardViewAction.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/OnBehalfOf.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/ReadReceiptInfo.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/SigninStateVerificationQuery.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/Surface.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/SurfaceType.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabContext.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabEntityContext.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabRequest.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabResponse.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabResponseCard.cs (92%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabResponseCards.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabResponsePayload.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabSubmit.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabSubmitData.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TabSuggestedActions.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TargetedMeetingNotification.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TargetedMeetingNotificationValue.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleAction.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleCardResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleContinueResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleMessageResponse.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleRequest.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleRequestContext.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleResponse.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleResponseBase.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TaskModuleTaskInfo.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamDetails.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamInfo.cs (96%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamMember.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsChannelAccount.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsChannelData.cs (99%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsChannelDataSettings.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsMeetingInfo.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsMeetingMember.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsMeetingParticipant.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsPagedMembersResult.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TeamsParticipantChannelAccount.cs (98%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/TenantInfo.cs (94%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Models/UserMeetingDetails.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/README.md (56%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/MessagingExtensionActionResponseConverter.cs (79%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/MessagingExtensionAttachmentConverter.cs (78%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/SurfaceConverter.cs (82%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TabSubmitDataConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TaskModuleCardResponseConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TaskModuleContinueResponseConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TaskModuleMessageResponseConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TaskModuleResponseBaseConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TaskModuleResponseConverter.cs (93%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/Converters/TeamsChannelDataConverter.cs (95%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/SerializationInit.cs (85%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/Serialization/SerializerExtensions.cs (89%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/TeamsActivityExtensions.cs (97%) rename src/libraries/{Partner/Microsoft.Agents.Teams => Extensions/Microsoft.Agents.Extensions.Teams}/TeamsChannelServiceClientFactory.cs (98%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/AppBasedLinkQueryTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/AttachmentExtensionsTests.cs (95%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/BotConfigAuthTests.cs (83%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/CacheInfoTests.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ChannelInfoTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ConfigAuthResponseTests.cs (83%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ConfigResponseTests.cs (81%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ConfigTaskResponseTests.cs (83%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/Connector/RestTeamsOperationsTests.cs (99%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/Connector/RetryActionTests.cs (96%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/Connector/RetryParamTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ConversationListTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/FileConsentCardResponseTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/FileConsentCardTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/FileDownloadInfoTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/FileInfoCardTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/FileUploadInfoTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MeetingParticipantInfoTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MeetingParticipantsEventDetailsTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadAppTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadAttachmentTests.cs (94%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadBodyTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadConversationsTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadFromTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadMentionTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadReactionTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadTests.cs (98%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessageActionsPayloadUserTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionActionResponseTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionActionTests.cs (95%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionAttachmentTests.cs (94%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionParametersTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionQueryOptionsTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionQueryTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionResponseTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionResultTests.cs (94%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/MessagingExtensionSuggestedActionTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj => Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj} (79%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/NotImplementedAdapter.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/NotificationInfoTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardActionBaseTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardActionCardTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardActionQueryTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardDateInputTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardFactTests.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardHttpPOSTTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardImageTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardInputBaseTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardMultichoiceInputChoiceTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardMultichoiceInputTests.cs (95%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardOpenUriTargetTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardOpenUriTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardSectionTests.cs (96%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardTests.cs (94%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardTextInputTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/O365ConnectorCardViewActionTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/ReadReceiptInfoTests.cs (88%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/SigninStateVerificationQueryTests.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/SimpleAdapter.cs (98%) create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabContextTests.cs (84%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabEntityContextTests.cs (86%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabRequestTests.cs (83%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabResponseCardTests.cs (85%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabResponseCardsTests.cs (79%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabResponsePayloadTests.cs (80%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabResponseTests.cs (77%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabSubmitDataTests.cs (85%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabSubmitTests.cs (83%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabSuggestedActionsTests.cs (81%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TabsTestData.cs (97%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleActionTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleCardResponseTests.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleContinueResponseTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleMessageResponseTests.cs (90%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleRequestContextTests.cs (89%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleRequestTests.cs (91%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleResponseBaseTests.cs (88%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleResponseTests.cs (96%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TaskModuleTaskInfoTests.cs (97%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamDetailsTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamInfoTests.cs (88%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsActivityHandlerTests.cs (99%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsChannelAccountTests.cs (96%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsChannelDataTests.cs (69%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsMeetingInfoTests.cs (88%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsMeetingParticipantTests.cs (92%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsPagedMembersResultTests.cs (93%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TeamsParticipantChannelAccountTests.cs (95%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TenantInfoTests.cs (88%) rename src/tests/{Microsoft.Agents.Teams.Tests => Microsoft.Agents.Extensions.Teams.Tests}/TestActivityHandler.cs (99%) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 5d98dbfd..5e2ddbb7 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -46,7 +46,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Model.Tests", "tests\Microsoft.Agents.Model.Tests\Microsoft.Agents.Model.Tests.csproj", "{53FEE87D-2E52-4064-93DB-BD27E3996B59}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Teams.Tests", "tests\Microsoft.Agents.Teams.Tests\Microsoft.Agents.Teams.Tests.csproj", "{D790E414-979D-4FF9-8EB6-08C205423037}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Extensions.Teams.Tests", "tests\Microsoft.Agents.Extensions.Teams.Tests\Microsoft.Agents.Extensions.Teams.Tests.csproj", "{D790E414-979D-4FF9-8EB6-08C205423037}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Authentication.Msal", "libraries\Authentication\Authentication.Msal\Microsoft.Agents.Authentication.Msal.csproj", "{AC905963-D232-4679-888B-22A3D6D39ACB}" EndProject @@ -134,11 +134,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeetingContextApp", "sample EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Authentication.Msal.Tests", "tests\Microsoft.Agents.Authentication.Msal.Tests\Microsoft.Agents.Authentication.Msal.Tests.csproj", "{B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Partner", "Partner", "{32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Teams", "libraries\Partner\Microsoft.Agents.Teams\Microsoft.Agents.Teams.csproj", "{261310F8-6D5E-4ECC-9250-95DDD7F3B1F1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.Teams", "libraries\Extensions\Microsoft.Agents.Extensions.Teams\Microsoft.Agents.Extensions.Teams.csproj", "{261310F8-6D5E-4ECC-9250-95DDD7F3B1F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.SharePoint", "libraries\Partner\Microsoft.Agents.SharePoint\Microsoft.Agents.SharePoint.csproj", "{790020D4-42D2-40EB-87F0-9DC2A1E6C01D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.SharePoint", "libraries\Extensions\Microsoft.Agents.Extensions.SharePoint\Microsoft.Agents.Extensions.SharePoint.csproj", "{790020D4-42D2-40EB-87F0-9DC2A1E6C01D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{B748D33C-AC75-4AEE-9305-34D1A0126202}" EndProject diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index 1793a705..b3e7827f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 010a970f..bf947b1c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -42,7 +42,6 @@ public Application(ApplicationOptions options) { ArgumentNullException.ThrowIfNull(options); - Options = options; if (Options.TurnStateFactory == null) @@ -525,7 +524,6 @@ public void StartTypingTimer(ITurnContext turnContext) { _typingTimer.Start(turnContext); } - } /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index 059f0a34..2c16f7ba 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -20,6 +20,11 @@ internal class TypingTimer : IDisposable /// private readonly int _interval; + /// + /// Initial delay before first typing is sent. + /// + private readonly int _initialDelay; + /// /// To detect redundant calls /// @@ -29,9 +34,11 @@ internal class TypingTimer : IDisposable /// Constructs a new instance of the class. /// /// The interval in milliseconds to send "typing" activity. - public TypingTimer(int interval = 1000) + /// Initial delay + public TypingTimer(int interval = 1000, int initialDelay = 500) { _interval = interval; + _initialDelay = initialDelay; } /// @@ -52,7 +59,7 @@ public bool Start(ITurnContext turnContext) return false; } - // Listen for outgoing activities + // Stop timer when message activities are sent turnContext.OnSendActivities(StopTimerWhenSendMessageActivityHandlerAsync); // Start periodically send "typing" activity @@ -105,7 +112,7 @@ private async void SendTypingActivity(object state) try { - await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }); + await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing, RelatesTo = turnContext.Activity.RelatesTo, Text = "TYPING" }); if (IsRunning()) { _timer?.Change(_interval, Timeout.Infinite); diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointActivityHandler.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointActivityHandler.cs index 034dc99a..1c6a8bd7 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointActivityHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointActivityHandler.cs @@ -5,13 +5,13 @@ using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.SharePoint.Models; +using Microsoft.Agents.Extensions.SharePoint.Models; using System; using System.Net; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.SharePoint.Compat +namespace Microsoft.Agents.Extensions.SharePoint.Compat { /// /// The SharePointActivityHandler is derived from ActivityHandler. It adds support for diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs index 3a88cf48..95ca81c8 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Compat/SharePointSSOTokenExchangeMiddleware.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Storage; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.SharePoint.Models; +using Microsoft.Agents.Extensions.SharePoint.Models; using System; using System.Collections.Generic; using System.Net; @@ -12,10 +12,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Connector; -using Microsoft.Agents.SharePoint.Compat; +using Microsoft.Agents.Extensions.SharePoint.Compat; using Microsoft.Agents.BotBuilder; -namespace Microsoft.Agents.SharePoint +namespace Microsoft.Agents.Extensions.SharePoint { /// /// If the activity name is cardExtension/token, this middleware will attempt to diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Microsoft.Agents.Extensions.SharePoint.csproj similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Microsoft.Agents.Extensions.SharePoint.csproj index 2b3b801a..4bd59a90 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Microsoft.Agents.SharePoint.csproj +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Microsoft.Agents.Extensions.SharePoint.csproj @@ -9,7 +9,7 @@ - Microsoft.Agents.SharePoint + Microsoft.Agents.Extensions.SharePoint Library for creating SharePoint agents using Microsoft Agent SDK Library for building SharePoint agents using Microsoft Agents SDK diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceData.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceData.cs index 46914976..ad2c0f14 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceData.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint Ace Data object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceRequest.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceRequest.cs index 4dcd6dbd..fcc5ae37 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/AceRequest.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/AceRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// ACE invoke request payload. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/BaseAction.cs similarity index 89% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/BaseAction.cs index e9d74a30..2fbb3143 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/BaseAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/BaseAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Base Action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ConfirmationDialog.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ConfirmationDialog.cs index 17cd44cd..5acacb46 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ConfirmationDialog.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ConfirmationDialog.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint Confirmation Dialog object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExecuteAction.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExecuteAction.cs index 94cb5afa..e2c95c73 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExecuteAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExecuteAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Action.Execute. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkAction.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkAction.cs index bdea0f2d..c900d7e0 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkAction.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint external link action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkActionParameters.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkActionParameters.cs index 7815b3e9..12a84a80 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ExternalLinkActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ExternalLinkActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint parameters for an External Link action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/FocusParameters.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/FocusParameters.cs index f2471fad..731ca4a6 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/FocusParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/FocusParameters.cs @@ -3,7 +3,7 @@ using System.Runtime.Serialization; -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint focus parameters. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationAction.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationAction.cs index 53d48cfd..3030f754 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint get location action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationActionParameters.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationActionParameters.cs index 2c8a025a..8793319c 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/GetLocationActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/GetLocationActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint parameters for a Get Location action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IAction.cs similarity index 85% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IAction.cs index 711938f1..3f8396d8 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Interface for actions. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ICardActionParameters.cs similarity index 86% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ICardActionParameters.cs index fa38c40d..a403de0d 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ICardActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ICardActionParameters.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Interface for card action parameters. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IOnCardSelectionAction.cs similarity index 86% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IOnCardSelectionAction.cs index 46f4b67d..6854dfc0 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/IOnCardSelectionAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/IOnCardSelectionAction.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Interface for action upon card selection. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/Location.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/Location.cs index 6c13fd28..4a6f5f2d 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/Location.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/Location.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Sharepoint Location object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewAction.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewAction.cs index 17cfa107..57315ecb 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint Quick View action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewActionParameters.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewActionParameters.cs index d58501f7..a86b5185 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/QuickViewActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/QuickViewActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint parameters for an quick view action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaAction.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaAction.cs index ab5874ce..af3adf5c 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint select media action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaActionParameters.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaActionParameters.cs index 93700dd4..9fae8750 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SelectMediaActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SelectMediaActionParameters.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint parameters for a select media action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationAction.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationAction.cs index 797e8ded..ce670243 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationAction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint show location action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationActionParameters.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationActionParameters.cs index 7659d676..6bb4bffa 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/ShowLocationActionParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/ShowLocationActionParameters.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// SharePoint parameters for a show location action. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SubmitAction.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SubmitAction.cs index 6def9f8a..b5c15020 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/Actions/SubmitAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/Actions/SubmitAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models.Actions +namespace Microsoft.Agents.Extensions.SharePoint.Models.Actions { /// /// Action.Submit. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/BaseHandleActionResponse.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/BaseHandleActionResponse.cs index d5e10f1b..7a71e674 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/BaseHandleActionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/BaseHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Adaptive Card Extension View response type. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/BaseCardComponent.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/BaseCardComponent.cs index 5c87748e..1fc38ceb 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/BaseCardComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/BaseCardComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Base class for Adaptive Card Extensions card view components. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardBarComponent.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardBarComponent.cs index 60866722..2c9199e5 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardBarComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardBarComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension card bar component. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardButtonComponent.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardButtonComponent.cs index 81ec6861..f608431e 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardButtonComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardButtonComponent.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Names of the supported Adaptive Card Extension Card View button styles. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardComponentName.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardComponentName.cs index 6a8ee4a3..7d3d0342 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardComponentName.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardComponentName.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Names of the supported Adaptive Card Extension Card View Components. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardImage.cs similarity index 90% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardImage.cs index f5c1d2b6..118e282f 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardImage.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardImage.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Properties for the image rendered in a card view. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxButton.cs similarity index 82% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxButton.cs index 771a053f..e9d5ba2b 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxButton.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxButton.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Card Search box button. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxComponent.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxComponent.cs index 63cdc547..89d38bb2 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchBoxComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchBoxComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension search box component. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchFooterComponent.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchFooterComponent.cs index 54a7db75..8988cad3 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardSearchFooterComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardSearchFooterComponent.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; using System; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension search footer component. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextComponent.cs similarity index 91% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextComponent.cs index f657b573..14b360c3 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension card text component. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputBaseButton.cs similarity index 83% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputBaseButton.cs index 2c0478a8..7615bc47 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputBaseButton.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputBaseButton.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Base Card text input button class. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputComponent.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputComponent.cs index 7b316f8f..881fcbd9 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputComponent.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputComponent.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension text input component. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputIconButton.cs similarity index 87% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputIconButton.cs index 03841582..5b5a1895 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputIconButton.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputIconButton.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Card text input button with icon. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputTitleButton.cs similarity index 87% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputTitleButton.cs index 8f9d5339..5ad494e7 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardTextInputTitleButton.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardTextInputTitleButton.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Card text input button with text. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardViewParameters.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardViewParameters.cs index e9438c6c..7840d6e4 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/CardViewParameters.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/CardViewParameters.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Adaptive Card Extension Card View Parameters. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/ICardButtonBase.cs similarity index 83% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/ICardButtonBase.cs index 1242d90f..b5b8e0d7 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardView/ICardButtonBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardView/ICardButtonBase.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; -namespace Microsoft.Agents.SharePoint.Models.CardView +namespace Microsoft.Agents.Extensions.SharePoint.Models.CardView { /// /// Base properties for the buttons used in Adaptive Card Extensions card view components. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewHandleActionResponse.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewHandleActionResponse.cs index d45f2bdc..f4083a0c 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewHandleActionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Adaptive Card Extension Client-side action response to render card view. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewResponse.cs similarity index 88% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewResponse.cs index 888fe545..9593e29c 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/CardViewResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/CardViewResponse.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; -using Microsoft.Agents.SharePoint.Models.CardView; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.CardView; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint Card View Data object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs index 87b52bdd..d6716409 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/GetPropertyPaneConfigurationResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint GetPropertyPaneConfiguration response object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneFieldProperties.cs similarity index 88% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneFieldProperties.cs index e089bdca..c1b5ed4a 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneFieldProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneFieldProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Interface for property pane field properties. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs similarity index 88% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs index 0381a888..8d43aeca 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/IPropertyPaneGroupOrConditionalGroup.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Interface for property pane group or conditional group. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/NoOpHandleActionResponse.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/NoOpHandleActionResponse.cs index 591fd5cb..10d56452 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/NoOpHandleActionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/NoOpHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Adaptive Card Extension Client-side action no-op response. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneCheckboxProperties.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneCheckboxProperties.cs index e3720d83..ffc19692 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneCheckboxProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneCheckboxProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane checkbox properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs index 97493eae..506a9f4e 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupIconProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane choice group icon properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs index f89fa01e..cb8cc7b1 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupImageSize.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane choice group image size object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupOption.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupOption.cs index c709841c..6f340898 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupOption.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupOption.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane choice group option object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs index 8b5d7cea..ed27aec6 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneChoiceGroupProperties.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane choice group properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownOption.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownOption.cs index 14dced25..c1781863 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownOption.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownOption.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane drop down option object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownProperties.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownProperties.cs index 1a88ea4a..f7ae9441 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneDropDownProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneDropDownProperties.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane drop down properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroup.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroup.cs index 1b665f2f..4c244779 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroup.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane group object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroupField.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroupField.cs index 66a2dc5e..4d34950d 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneGroupField.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneGroupField.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane group field object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLabelProperties.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLabelProperties.cs index 00425661..03ce4b49 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLabelProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLabelProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane label properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs index 8b6b7a8e..d584e7fe 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkPopupWindowProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane link popup window properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkProperties.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkProperties.cs index a5dee509..ddefe214 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneLinkProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneLinkProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane link properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePage.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePage.cs index b04e7e9b..d264972c 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePage.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePage.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane page object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePageHeader.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePageHeader.cs index f942c150..55d878d5 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPanePageHeader.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPanePageHeader.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane page header object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneSliderProperties.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneSliderProperties.cs index f4e4c13d..e1047d25 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneSliderProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneSliderProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane slider properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneTextFieldProperties.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneTextFieldProperties.cs index 77dcc9dd..614a7673 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneTextFieldProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneTextFieldProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane text field properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneToggleProperties.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneToggleProperties.cs index 48844949..07f7c9f5 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/PropertyPaneToggleProperties.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/PropertyPaneToggleProperties.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint property pane toggle properties object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewData.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewData.cs index 6531160c..a2772eec 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewData.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint Quick View Data object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewHandleActionResponse.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewHandleActionResponse.cs index bdb6fdc6..8fb1c9f5 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewHandleActionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewHandleActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// Adaptive Card Extension Client-side action response to render quick view. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewResponse.cs index 805a1e65..f7f45504 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Models/QuickViewResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Models/QuickViewResponse.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Models.Actions; +using Microsoft.Agents.Extensions.SharePoint.Models.Actions; -namespace Microsoft.Agents.SharePoint.Models +namespace Microsoft.Agents.Extensions.SharePoint.Models { /// /// SharePoint GetQuickView response object. diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/README.md b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/README.md similarity index 55% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/README.md rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/README.md index acfc8d0b..6a2a84e1 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/README.md +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/README.md @@ -1,4 +1,4 @@ -# Microsoft.Agents.SharePoint +# Microsoft.Agents.Extensions.SharePoint ## About diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceDataConverter.cs similarity index 84% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceDataConverter.cs index c722074b..480315c8 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceDataConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceDataConverter.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.SharePoint.Models; +using Microsoft.Agents.Extensions.SharePoint.Models; using System.Text.Json; -namespace Microsoft.Agents.SharePoint.Serialization.Converters +namespace Microsoft.Agents.Extensions.SharePoint.Serialization.Converters { internal class AceDataConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceRequestConverter.cs similarity index 85% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceRequestConverter.cs index 2660d397..15c265a3 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/Converters/AceRequestConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/Converters/AceRequestConverter.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.SharePoint.Models; +using Microsoft.Agents.Extensions.SharePoint.Models; using System.Text.Json; -namespace Microsoft.Agents.SharePoint.Serialization.Converters +namespace Microsoft.Agents.Extensions.SharePoint.Serialization.Converters { internal class AceRequestConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializationInit.cs similarity index 85% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializationInit.cs index e9a34362..450879d3 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializationInit.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializationInit.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Serialization; -namespace Microsoft.Agents.SharePoint.Serialization +namespace Microsoft.Agents.Extensions.SharePoint.Serialization { [SerializationInit] internal class SerializationInit diff --git a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializerExtensions.cs similarity index 77% rename from src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializerExtensions.cs index 35cf5c23..c2c96d53 100644 --- a/src/libraries/Partner/Microsoft.Agents.SharePoint/Serialization/SerializerExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.SharePoint/Serialization/SerializerExtensions.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.SharePoint.Serialization.Converters; +using Microsoft.Agents.Extensions.SharePoint.Serialization.Converters; using System.Text.Json; -namespace Microsoft.Agents.SharePoint.Serialization +namespace Microsoft.Agents.Extensions.SharePoint.Serialization { internal static class SerializerExtensions { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ConfigHandlerAsync.cs similarity index 78% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ConfigHandlerAsync.cs index 7e091534..2cefee97 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/ConfigHandlerAsync.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ConfigHandlerAsync.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Function for handling config events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopData.cs similarity index 87% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopData.cs index 4442445b..568f4db0 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopData.cs @@ -1,5 +1,7 @@ - -namespace Microsoft.Agents.Teams.App +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Data returned when the thumbsup or thumbsdown button is clicked and response is received. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopHandler.cs similarity index 82% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopHandler.cs index da19002b..be1a91db 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/FeedbackLoopHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FeedbackLoopHandler.cs @@ -1,9 +1,12 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Function for feedback loop activites diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FileConsentCardHandler.cs similarity index 80% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FileConsentCardHandler.cs index 0416f5d9..456d70db 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/FileConsentCardHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/FileConsentCardHandler.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Function for handling file consent card activities. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs similarity index 85% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs index 0c60f84d..6e9052d2 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/IInputFileDownloader.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs @@ -1,11 +1,13 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// A plugin responsible for downloading files relative to the current user's input. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs similarity index 87% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs index bf2428f9..119d589a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/InputFile.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs @@ -1,7 +1,9 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using System; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Represents an upload file diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs index bd33e035..5175c6e9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs @@ -1,11 +1,14 @@ -using Microsoft.Agents.BotBuilder.App; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.Meetings +namespace Microsoft.Agents.Extensions.Teams.App.Meetings { /// /// Meetings class to enable fluent style registration of handlers related to Microsoft Teams Meetings. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsHandlers.cs similarity index 91% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsHandlers.cs index 08dd9510..69147925 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/Meetings/MeetingsHandlers.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsHandlers.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.Meetings +namespace Microsoft.Agents.Extensions.Teams.App.Meetings { /// /// Function for handling Microsoft Teams meeting start events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index 865a5747..75e003e4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -1,17 +1,20 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.MessageExtensions +namespace Microsoft.Agents.Extensions.Teams.App.MessageExtensions { /// /// Constants for message extension invoke names diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs index e57cc3fa..094fad3c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsHandlers.cs @@ -1,13 +1,16 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.MessageExtensions +namespace Microsoft.Agents.Extensions.Teams.App.MessageExtensions { /// /// Function for handling Message Extension submitAction events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/O365ConnectorCardActionHandler.cs similarity index 79% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/O365ConnectorCardActionHandler.cs index 502ad24d..643f2df1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/O365ConnectorCardActionHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/O365ConnectorCardActionHandler.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Function for handling O365 Connector Card Action activities. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ReadReceiptHandler.cs similarity index 76% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ReadReceiptHandler.cs index 82b0013b..220c8f98 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/ReadReceiptHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/ReadReceiptHandler.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Function for handling read receipt events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs index 24c4738c..188572c4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs @@ -1,15 +1,18 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.TaskModules +namespace Microsoft.Agents.Extensions.Teams.App.TaskModules { /// /// TaskModules class to enable fluent style registration of handlers related to Task Modules. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesHandlers.cs similarity index 86% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesHandlers.cs index 0ce2106e..a7dae1ea 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesHandlers.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesHandlers.cs @@ -1,10 +1,13 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.App.TaskModules +namespace Microsoft.Agents.Extensions.Teams.App.TaskModules { /// /// Function for handling Task Module fetch events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesOptions.cs similarity index 77% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesOptions.cs index d5721c8b..2ab75c03 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/TaskModules/TaskModulesOptions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesOptions.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Agents.Teams.App.TaskModules +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.Extensions.Teams.App.TaskModules { /// /// Options for TaskModules class. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs index fa32cadb..d0f95889 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsApplication.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs @@ -1,18 +1,21 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Teams.App.Meetings; -using Microsoft.Agents.Teams.App.MessageExtensions; -using Microsoft.Agents.Teams.App.TaskModules; +using Microsoft.Agents.Extensions.Teams.App.Meetings; +using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; +using Microsoft.Agents.Extensions.Teams.App.TaskModules; -namespace Microsoft.Agents.Teams.App +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Application class for routing and processing incoming requests. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsConversationUpdateEvents.cs similarity index 90% rename from src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsConversationUpdateEvents.cs index 9d842c59..40f6af3a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/App/TeamsConversationUpdateEvents.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsConversationUpdateEvents.cs @@ -1,6 +1,9 @@ -using Microsoft.Agents.BotBuilder.App; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -namespace Microsoft.Agents.Teams.App +using Microsoft.Agents.BotBuilder.App; + +namespace Microsoft.Agents.Extensions.Teams.App { /// /// Conversation update events. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AssemblyInfo.cs similarity index 62% rename from src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AssemblyInfo.cs index 19d78efc..c9914afa 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/AssemblyInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AssemblyInfo.cs @@ -5,4 +5,4 @@ // Allows us to access some internal methods from the Memory.Tests unit tests so we don't have to use reflection and we get compile checks. [assembly: InternalsVisibleTo("Microsoft.Agents.Connector.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo("Microsoft.Agents.Teams.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.Agents.Extensions.Teams.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AttachmentExtensions.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AttachmentExtensions.cs index ddfe36a4..935d253c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/AttachmentExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/AttachmentExtensions.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Teams +namespace Microsoft.Agents.Extensions.Teams { /// /// Attachment extensions. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsActivityHandler.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsActivityHandler.cs index 9cab53f6..6c2bba6b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsActivityHandler.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsActivityHandler.cs @@ -6,8 +6,8 @@ using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Connector; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections.Generic; using System.Linq; using System.Net; @@ -15,7 +15,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Compat +namespace Microsoft.Agents.Extensions.Teams.Compat { /// /// The TeamsActivityHandler is derived from ActivityHandler. It adds support for diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs index b0ba17c1..e2eb1fd1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Compat/TeamsSSOTokenExchangeMiddleware.cs @@ -12,7 +12,7 @@ using Microsoft.Agents.Connector; using Microsoft.Agents.BotBuilder; -namespace Microsoft.Agents.Teams.Compat +namespace Microsoft.Agents.Extensions.Teams.Compat { /// /// If the activity name is signin/tokenExchange, this middleware will attempt to diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs similarity index 91% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs index 1f2082f5..d4a0857d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsConnectorClient.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Connector; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// The Connector for Microsoft Teams allows your bot to perform extended operations on a Microsoft Teams channel. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsOperations.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsOperations.cs index 38addb86..c3db0d4d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ITeamsOperations.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsOperations.cs @@ -2,12 +2,12 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// TeamsOperations operations. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs index cb623acb..580b1170 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsConnectorClient.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs @@ -7,7 +7,7 @@ using System.Net.Http; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// TeamsConnectorClient REST implementation. This ConnectorClient is suitable for either ABS or SMBA. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs index 5711b814..4eab666c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RestTeamsOperations.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; using System.Net.Http; @@ -13,7 +13,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// TeamsOperations operations. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryAction.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryAction.cs index 7f0920ae..bfc8e831 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryAction.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// Retries asynchronous operations. In case of errors, it collects and returns exceptions in an AggregateException object. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryParams.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryParams.cs index e7b2fd4c..ac6cff2f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/RetryParams.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RetryParams.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// Wrapper class that defines a retrying behavior. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs index 44fdebe5..97198db1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TeamsInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs @@ -5,14 +5,14 @@ using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// The TeamsInfo Test If Build Remote Successful diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ThrottleException.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ThrottleException.cs index 10cf018f..8d6e5e8f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/ThrottleException.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ThrottleException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// Custom throttling exception. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TimeSpanExtensions.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TimeSpanExtensions.cs index 7616e7d1..73d14b77 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Connector/TimeSpanExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TimeSpanExtensions.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Connector +namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// Extension methods for the class. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Microsoft.Agents.Extensions.Teams.csproj similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Microsoft.Agents.Extensions.Teams.csproj index b5156eb9..e0334daf 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Microsoft.Agents.Teams.csproj +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Microsoft.Agents.Extensions.Teams.csproj @@ -9,7 +9,7 @@ - Microsoft.Agents.Teams + Microsoft.Agents.Extensions.Teams Library for creating Teams agents using Microsoft Agent SDK Library for building Teams agents using Microsoft Agents SDK diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/AppBasedLinkQuery.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/AppBasedLinkQuery.cs index 459bd806..9c87d1d3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/AppBasedLinkQuery.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/AppBasedLinkQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Invoke request body type for app-based link query. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntriesResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntriesResponse.cs index fb610f53..cc187615 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntriesResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntriesResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies the failed entries response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntry.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntry.cs index 44881ec5..8c36e90c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchFailedEntry.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchFailedEntry.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies the failed entry with its id and error. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchOperationState.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchOperationState.cs index 357ce15c..a4728559 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BatchOperationState.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BatchOperationState.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Object representing operation state. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BotConfigAuth.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BotConfigAuth.cs index 58dcb014..342d933b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/BotConfigAuth.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/BotConfigAuth.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies bot config auth, including type and suggestedActions. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/CacheInfo.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/CacheInfo.cs index 0e248f44..709ecc7d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/CacheInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/CacheInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// A cache info object which notifies Teams how long an object should be cached for. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ChannelInfo.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ChannelInfo.cs index 4f2a5c16..c4def630 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ChannelInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ChannelInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// A channel info object which describes the channel. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigAuthResponse.cs similarity index 89% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigAuthResponse.cs index e2f12f0a..318dad64 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigAuthResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigAuthResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for Config Auth Response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponse.cs index 54bc4811..e1d7afe9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for Config Response Payload. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponseBase.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponseBase.cs index b0959633..35378598 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigResponseBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigResponseBase.cs @@ -3,7 +3,7 @@ -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies Invoke response base including response type. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigTaskResponse.cs similarity index 90% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigTaskResponse.cs index 0b065a99..7e8105f3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConfigTaskResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConfigTaskResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for Config Task Response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ContentType.cs similarity index 90% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ContentType.cs index 14bfffba..b72563ca 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ContentType.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ContentType.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Defines content type. Depending on contentType, content field will have a different structure. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConversationList.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConversationList.cs index 0db0a434..063ce857 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ConversationList.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ConversationList.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// List of channels under a team. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCard.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCard.cs index cd261095..fb5f63ff 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCard.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// File consent card attachment. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCardResponse.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCardResponse.cs index 88ba2604..6f46dc14 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileConsentCardResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileConsentCardResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents the value of the invoke activity sent when the user acts on diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileDownloadInfo.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileDownloadInfo.cs index d3dd1391..86f0e037 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileDownloadInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileDownloadInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// File download info attachment. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileInfoCard.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileInfoCard.cs index a2f5f7d7..7a0a59d3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileInfoCard.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileInfoCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// File info card. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileUploadInfo.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileUploadInfo.cs index 36250439..36efeea5 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/FileUploadInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/FileUploadInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Information about the file to be uploaded. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetails.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetails.cs index 1e46852f..567b9545 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetailsBase.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetailsBase.cs index e75ea1d6..bc8c0c59 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingDetailsBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingDetailsBase.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEndEventDetails.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEndEventDetails.cs index cf989853..8fc8afee 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEndEventDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEndEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a Teams meeting end event. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEventDetails.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEventDetails.cs index e7c01303..dec0d5a6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingEventDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingInfo.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingInfo.cs index 5fa740fa..28b98877 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingInfo.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// General information about a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotification.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotification.cs index 5b29fc08..a4c6621a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotification.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotification.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies Bot meeting notification including meeting notification value. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationBase.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationBase.cs index f4b2de28..aba666ff 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies Bot meeting notification base including channel data and type. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationChannelData.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationChannelData.cs index 825dc33b..40daaca3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationChannelData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationChannelData.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specify Teams Bot meeting notification channel data. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationRecipientFailureInfo.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationRecipientFailureInfo.cs index dd5d4d91..5a55dcfe 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationRecipientFailureInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationRecipientFailureInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Information regarding failure to notify a recipient of a meeting notification. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationResponse.cs index 7514f289..6e998508 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingNotificationResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingNotificationResponse.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies Bot meeting notification response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantInfo.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantInfo.cs index 6d6d90e7..124b0ed3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Teams meeting participant details. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantsEventDetails.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantsEventDetails.cs index 054dfb5f..bb83d79c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingParticipantsEventDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingParticipantsEventDetails.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Data about the meeting participants. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStageSurface.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStageSurface.cs index a3a98293..c8d4f241 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStageSurface.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStageSurface.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies meeting stage surface. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStartEventDetails.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStartEventDetails.cs index a75d78e1..e0160bd6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingStartEventDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingStartEventDetails.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a Teams meeting start event. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingTabIconSurface.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingTabIconSurface.cs index dd4c05ed..b99178a6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MeetingTabIconSurface.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MeetingTabIconSurface.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies meeting tab icon surface. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayload.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayload.cs index 2f3feefd..17ab177f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayload.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayload.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents the individual message within a chat or channel where a diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadApp.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadApp.cs index 2c620bf4..254db88a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadApp.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadApp.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents an application entity. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadAttachment.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadAttachment.cs index 5131efc5..80a36df1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadAttachment.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadAttachment.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents the attachment in a message. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadBody.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadBody.cs index e3cf198c..24d347a6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadBody.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadBody.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Plaintext/HTML representation of the content of the message. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadConversation.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadConversation.cs index 09658d89..f82a4325 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadConversation.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadConversation.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents a team or channel entity. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadFrom.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadFrom.cs index ffe99d8f..733b158d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadFrom.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadFrom.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents a user, application, or conversation type that either sent diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadMention.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadMention.cs index d1f52042..be24e6b8 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadMention.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadMention.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents the entity that was mentioned in the message. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadReaction.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadReaction.cs index b898466e..2d4ccb3a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadReaction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadReaction.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents the reaction of a user to a message. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadUser.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadUser.cs index 773db4c9..c75f3898 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessageActionsPayloadUser.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessageActionsPayloadUser.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents a user entity. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAction.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAction.cs index ca1becdc..d21174eb 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAction.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionActionResponse.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionActionResponse.cs index 54c50136..b484ca2e 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionActionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionActionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Response of messaging extension action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAttachment.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAttachment.cs index 64b06bb3..02aca1e1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionAttachment.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionAttachment.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension attachment. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionParameter.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionParameter.cs index 44c2a05f..04fb4d3b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionParameter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionParameter.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension query parameters. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQuery.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQuery.cs index f29344f5..4bf3f392 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQuery.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQuery.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension query. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQueryOptions.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQueryOptions.cs index 83db1096..f53a3e98 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionQueryOptions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionQueryOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension query options. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResponse.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResponse.cs index a03af542..742c9039 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResult.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResult.cs index 7335dc86..2e1306c5 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionResult.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionResult.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension result. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionSuggestedAction.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionSuggestedAction.cs index 57169a47..7d50a611 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/MessagingExtensionSuggestedAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/MessagingExtensionSuggestedAction.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Messaging extension Actions (Only when type is auth or config). diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/NotificationInfo.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/NotificationInfo.cs index 9859b62a..574da6b3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/NotificationInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/NotificationInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies if a notification is to be sent for the mentions. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCard.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCard.cs index d626f37b..085090cd 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCard.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCard.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionBase.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionBase.cs index 17356ec1..24b3105e 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card action base. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionCard.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionCard.cs index 5635ab53..5057747e 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionCard.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionCard.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card ActionCard action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionQuery.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionQuery.cs index 1d877086..662fd6ed 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardActionQuery.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardActionQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card HttpPOST invoke query. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardDateInput.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardDateInput.cs index 5bd15d77..58faa12d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardDateInput.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardDateInput.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card date input. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardFact.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardFact.cs index 5fe9e86d..6566b589 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardFact.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardFact.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card fact. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardHttpPOST.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardHttpPOST.cs index 4a69224e..5d51d322 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardHttpPOST.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardHttpPOST.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card HttpPOST action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardImage.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardImage.cs index 20f2f15c..d36b125d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardImage.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardImage.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card image. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardInputBase.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardInputBase.cs index 6db8011b..7da5c1d7 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardInputBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardInputBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card input for ActionCard action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInput.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInput.cs index 28d3a2d9..97dc7cb4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInput.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInput.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card multiple choice input. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs index 02e6b24a..6d65130b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardMultichoiceInputChoice.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365O365 connector card multiple choice input item. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUri.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUri.cs index b687917a..08dbe3fa 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUri.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUri.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card OpenUri action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUriTarget.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUriTarget.cs index b6bd4f75..f068adc0 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardOpenUriTarget.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardOpenUriTarget.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card OpenUri target. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardSection.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardSection.cs index ff791ae0..eba04c50 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardSection.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardSection.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card section. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardTextInput.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardTextInput.cs index 0a6e5f70..40a3a6db 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardTextInput.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardTextInput.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card text input. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardViewAction.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardViewAction.cs index 8e8fc31f..b71c55fc 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/O365ConnectorCardViewAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/O365ConnectorCardViewAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// O365 connector card ViewAction action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/OnBehalfOf.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/OnBehalfOf.cs index 53473ada..e4dbe773 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/OnBehalfOf.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/OnBehalfOf.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies attribution for notifications. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ReadReceiptInfo.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ReadReceiptInfo.cs index 97998cd2..01f0bd67 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/ReadReceiptInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/ReadReceiptInfo.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// General information about a read receipt. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SigninStateVerificationQuery.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SigninStateVerificationQuery.cs index a35d80e7..9e9b46a2 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/SigninStateVerificationQuery.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SigninStateVerificationQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Signin state (part of signin action auth flow) verification invoke query. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/Surface.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/Surface.cs index 4d4537a7..b8ca642d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/Surface.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/Surface.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies where the notification will be rendered in the meeting UX. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SurfaceType.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SurfaceType.cs index 7feb977b..3e6300e8 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/SurfaceType.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/SurfaceType.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Defines Teams Surface type for use with a object. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabContext.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabContext.cs index 1de9d66f..6795021b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabContext.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Current tab request context, i.e., the current theme. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabEntityContext.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabEntityContext.cs index 55596e83..f88d3a09 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabEntityContext.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabEntityContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Current TabRequest entity context, or 'tabEntityId'. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabRequest.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabRequest.cs index 403e2800..2e4c1bf4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabRequest.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Invoke ('tab/fetch') request value payload. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponse.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponse.cs index 8a6b7fdd..133ba905 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for Card Tab Response Payload. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCard.cs similarity index 92% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCard.cs index 1d553daa..935201c9 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCard.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCard.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for cards for a Tab request. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCards.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCards.cs index 14972198..88789b7f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponseCards.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponseCards.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for cards for a . diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponsePayload.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponsePayload.cs index 3402b75b..b3802734 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabResponsePayload.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabResponsePayload.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Payload for Tab Response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmit.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmit.cs index 92cc7727..1a225eed 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmit.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmit.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Invoke ('tab/submit') request value payload. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmitData.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmitData.cs index 99549868..a7a50a76 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSubmitData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSubmitData.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Invoke ('tab/submit') request value payload data. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSuggestedActions.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSuggestedActions.cs index 659787f9..a3e613ad 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TabSuggestedActions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TabSuggestedActions.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Tab SuggestedActions (Only when type is 'auth' or 'silentAuth'). diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotification.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotification.cs index 562d5e0b..ee1c7547 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotification.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotification.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies Teams targeted meeting notification. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotificationValue.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotificationValue.cs index 5e011f5f..309f7be3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TargetedMeetingNotificationValue.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TargetedMeetingNotificationValue.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specifies the targeted meeting notification value, including recipients and surfaces. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleAction.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleAction.cs index 5c64840b..acaec2b0 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleAction.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleAction.cs @@ -7,7 +7,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Adapter class to represent BotBuilder card action as adaptive card action (in type of Action.Submit). diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleCardResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleCardResponse.cs index a7a8cdce..4e037215 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleCardResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleCardResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License.s -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Tab Response to 'task/submit' from a tab. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleContinueResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleContinueResponse.cs index 0a22b01b..7d2f645c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleContinueResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleContinueResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Task Module Response with continue action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleMessageResponse.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleMessageResponse.cs index ba566952..486aa85e 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleMessageResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleMessageResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Task Module response with message action. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequest.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequest.cs index 7a586723..f5d91544 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequest.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Task module invoke request value payload. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequestContext.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequestContext.cs index bc530aae..fdd515b4 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleRequestContext.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleRequestContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Current user context, i.e., the current theme. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponse.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponse.cs index 510e3bbe..153c0f62 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponse.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Envelope for Task Module Response. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponseBase.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponseBase.cs index 67b93039..9959bf01 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleResponseBase.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleResponseBase.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Base class for Task Module responses. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleTaskInfo.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleTaskInfo.cs index 7e3276c0..fab925d7 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TaskModuleTaskInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TaskModuleTaskInfo.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Metadata for a Task Module. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamDetails.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamDetails.cs index b8a56eab..8d2df139 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamDetails.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Details related to a team. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamInfo.cs similarity index 96% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamInfo.cs index 46f86e20..61468c44 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Describes a team. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamMember.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamMember.cs index e88c08e8..934bc4e1 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamMember.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamMember.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Describes a member. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelAccount.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelAccount.cs index 029d3033..07b3d343 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelAccount.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelAccount.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Teams channel account detailing user Azure Active Directory details. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelData.cs similarity index 99% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelData.cs index d8b5a1e7..240c75c6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelData.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelData.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Channel data specific to messages received in Microsoft Teams. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs index 1174b23f..a72ab3f6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsChannelDataSettings.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Settings within teams channel data specific to messages received in Microsoft Teams. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingInfo.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingInfo.cs index 964c83f6..ec65b3ac 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Describes a Teams Meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingMember.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingMember.cs index 4120e129..2733ff2b 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingMember.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingMember.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Data about the meeting participants. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingParticipant.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingParticipant.cs index aa7f7c22..a3dd899d 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsMeetingParticipant.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsMeetingParticipant.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Teams meeting participant information, detailing user Azure Active Directory and meeting participant details. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsPagedMembersResult.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsPagedMembersResult.cs index b88fb7a6..c9b30471 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsPagedMembersResult.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsPagedMembersResult.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Represents a wrapper for a Teams members query result. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsParticipantChannelAccount.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsParticipantChannelAccount.cs index 124e603b..947ee6d7 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TeamsParticipantChannelAccount.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsParticipantChannelAccount.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Teams participant channel account detailing user Azure Active Directory and meeting participant details. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TenantInfo.cs similarity index 94% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TenantInfo.cs index d1aa1590..227c2c6c 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/TenantInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TenantInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Describes a tenant. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/UserMeetingDetails.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/UserMeetingDetails.cs index 299f964e..024929b5 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Models/UserMeetingDetails.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/UserMeetingDetails.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.Teams.Models +namespace Microsoft.Agents.Extensions.Teams.Models { /// /// Specific details of a user in a Teams meeting. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/README.md b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/README.md similarity index 56% rename from src/libraries/Partner/Microsoft.Agents.Teams/README.md rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/README.md index e01d025b..00462751 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/README.md +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/README.md @@ -1,4 +1,4 @@ -# Microsoft.Agents.Teams +# Microsoft.Agents.Extensions.Teams ## About diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs similarity index 79% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs index d113e838..831d2ee3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionActionResponseConverter.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponseBase. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs similarity index 78% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs index 6c90c6ac..7550117f 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/MessagingExtensionAttachmentConverter.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponseBase. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/SurfaceConverter.cs similarity index 82% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/SurfaceConverter.cs index 18e51b8a..7bce37a3 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/SurfaceConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/SurfaceConverter.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { /// /// Converter which allows json to be expression to object or static object. @@ -29,6 +28,7 @@ public override Surface Read(ref Utf8JsonReader reader, Type typeToConvert, Json SurfaceType surfaceType = SurfaceType.Unknown; ContentType contentType = ContentType.Unknown; + string tabEntityId = null; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) @@ -39,7 +39,8 @@ public override Surface Read(ref Utf8JsonReader reader, Type typeToConvert, Json if (reader.TokenType == JsonTokenType.PropertyName) { var propertyName = reader.GetString(); - if (string.Equals("type", propertyName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("type", propertyName, StringComparison.OrdinalIgnoreCase) + || string.Equals("surface", propertyName, StringComparison.OrdinalIgnoreCase)) { surfaceType = JsonSerializer.Deserialize(ref reader, options); } @@ -47,6 +48,10 @@ public override Surface Read(ref Utf8JsonReader reader, Type typeToConvert, Json { contentType = JsonSerializer.Deserialize(ref reader, options); } + else if (string.Equals("tabEntityId", propertyName, StringComparison.OrdinalIgnoreCase)) + { + tabEntityId = JsonSerializer.Deserialize(ref reader, options); + } } } @@ -58,7 +63,7 @@ public override Surface Read(ref Utf8JsonReader reader, Type typeToConvert, Json parsedSurface = CreateMeetingStageSurfaceWithContentType(contentType); break; case SurfaceType.MeetingTabIcon: - parsedSurface = new MeetingTabIconSurface(); + parsedSurface = new MeetingTabIconSurface() { TabEntityId = tabEntityId }; break; default: throw new ArgumentException($"Invalid surface type: {surfaceType}"); diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TabSubmitDataConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TabSubmitDataConverter.cs index 61849d45..dbc6b3ca 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TabSubmitDataConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TabSubmitDataConverter.cs @@ -6,9 +6,9 @@ using System.Text.Json; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TabSubmitDataConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs index e62da839..776bbd9a 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleCardResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TaskModuleCardResponseConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs index 6e01ae86..f6a4a406 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleContinueResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TaskModuleContinueResponseConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs index ce3884b6..1638cfb6 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleMessageResponseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TaskModuleMessageResponseConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs index 201eaa31..81a72f20 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseBaseConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TaskModuleResponseBaseConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseConverter.cs similarity index 93% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseConverter.cs index 72606582..b02b6821 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TaskModuleResponseConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TaskModuleResponseConverter.cs @@ -2,12 +2,12 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { // This is required because ConnectorConverter supports derived type handling. // In this case for the 'Task' property of type TaskModuleResponse. diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataConverter.cs similarity index 95% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataConverter.cs index a4c22570..b768e377 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/Converters/TeamsChannelDataConverter.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataConverter.cs @@ -3,12 +3,12 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Core.Serialization.Converters; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Reflection; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization.Converters +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters { internal class TeamsChannelDataConverter : ConnectorConverter { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializationInit.cs similarity index 85% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializationInit.cs index 99cb4651..5edec944 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializationInit.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializationInit.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Core.Serialization; -namespace Microsoft.Agents.Teams.Serialization +namespace Microsoft.Agents.Extensions.Teams.Serialization { [SerializationInit] internal class SerializationInit diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs similarity index 89% rename from src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs index 2411e257..911154c7 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/Serialization/SerializerExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Serialization.Converters; +using Microsoft.Agents.Extensions.Teams.Serialization.Converters; using System.Text.Json; -namespace Microsoft.Agents.Teams.Serialization +namespace Microsoft.Agents.Extensions.Teams.Serialization { internal static class SerializerExtensions { diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsActivityExtensions.cs similarity index 97% rename from src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsActivityExtensions.cs index 63909e4e..8d7985fd 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/TeamsActivityExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsActivityExtensions.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections.Generic; -namespace Microsoft.Agents.Teams +namespace Microsoft.Agents.Extensions.Teams { /// /// The TeamsActivityExtensions diff --git a/src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs similarity index 98% rename from src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs rename to src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs index 9a977878..7662e3cb 100644 --- a/src/libraries/Partner/Microsoft.Agents.Teams/TeamsChannelServiceClientFactory.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs @@ -10,12 +10,12 @@ using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector; -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Connector; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Microsoft.Agents.Teams +namespace Microsoft.Agents.Extensions.Teams { /// /// A factory to create REST clients to interact with a Channel Service. diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 7fda502d..3538e27d 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -65,6 +65,9 @@ public static IHostApplicationBuilder AddBot(thi // Add factory for ConnectorClient and UserTokenClient creation builder.Services.AddSingleton(); + // Add IStorage for turn state persistence + builder.Services.AddSingleton(); + // Add the ChannelAdapter, this is the default adapter that works with Azure Bot Service and Activity Protocol. AddCloudAdapter(builder.Services); @@ -97,9 +100,6 @@ public static IHostApplicationBuilder AddChannelHost(this IHostApplica sp.GetService(), (ILogger)sp.GetService(typeof(ILogger)))); - // Add IStorage for turn state persistence - builder.Services.AddSingleton(); - // Add conversation id factory. // This is a memory only implementation, and for production would require persistence. builder.Services.AddSingleton(); diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index c36e85b6..a0be49df 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -51,6 +51,8 @@ public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, ITurn /// public static async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { + await Task.Delay(5000); + // Increment count state. int count = turnState.Conversation.IncrementMessageCount(); diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs index c6c01ce9..ea7e5a54 100644 --- a/src/samples/Application/messaging.echoBot/Program.cs +++ b/src/samples/Application/messaging.echoBot/Program.cs @@ -1,6 +1,4 @@ using EchoBot; -using Microsoft.Agents.Authentication; -using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Hosting.AspNetCore; @@ -20,28 +18,19 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Create the storage to persist turn state -builder.Services.AddSingleton(); - -// Add Connections object to access configured token connections. -builder.Services.AddSingleton(); - -// Add factory for ConnectorClient and UserTokenClient creation -builder.Services.AddSingleton(); - -builder.Services.AddCloudAdapter(); - -// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. -builder.Services.AddTransient(sp => +// Create the bot as a transient. +builder.Services.AddTransient(sp => { - ApplicationOptions applicationOptions = new() + return new() { + StartTypingTimer = true, TurnStateFactory = () => new TurnState(sp.GetService()) }; - - return new EchoBotApplication(applicationOptions); }); +builder.AddBot(); + + var app = builder.Build(); if (app.Environment.IsDevelopment()) diff --git a/src/samples/AuthenticationBot/AuthenticationBot.csproj b/src/samples/AuthenticationBot/AuthenticationBot.csproj index 9886ce97..75cf8b34 100644 --- a/src/samples/AuthenticationBot/AuthenticationBot.csproj +++ b/src/samples/AuthenticationBot/AuthenticationBot.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index b3eba83d..d77df3ee 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore; using AuthenticationBot; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; diff --git a/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj b/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj index 35a9ecce..de5ba9a0 100644 --- a/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj +++ b/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs index 2d92c868..e13cbb8b 100644 --- a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs +++ b/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using AdaptiveCards.Templating; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; namespace AdaptiveCardActions.Bots diff --git a/src/samples/Teams/AdaptiveCardActions/Program.cs b/src/samples/Teams/AdaptiveCardActions/Program.cs index 03f7bd20..d42d61b9 100644 --- a/src/samples/Teams/AdaptiveCardActions/Program.cs +++ b/src/samples/Teams/AdaptiveCardActions/Program.cs @@ -4,7 +4,7 @@ using AdaptiveCardActions.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json b/src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json index 6a45ee49..ed776e5f 100644 --- a/src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json +++ b/src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.cardactions", + "packageName": "com.Microsoft.Agents.Extensions.Teams.cardactions", "developer": { "name": "Microsoft", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs index 68b6e577..2064999c 100644 --- a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs +++ b/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs @@ -10,13 +10,13 @@ using System.Xml; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Extensions.Configuration; using AdaptiveCards.Templating; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Connector.Types; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Connector; using Microsoft.Agents.BotBuilder; namespace ConversationBot.Bots diff --git a/src/samples/Teams/ConversationBot/ConversationBot.csproj b/src/samples/Teams/ConversationBot/ConversationBot.csproj index 7dc3a913..ec0c48f1 100644 --- a/src/samples/Teams/ConversationBot/ConversationBot.csproj +++ b/src/samples/Teams/ConversationBot/ConversationBot.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/samples/Teams/ConversationBot/Program.cs b/src/samples/Teams/ConversationBot/Program.cs index 79f90b0c..111ee673 100644 --- a/src/samples/Teams/ConversationBot/Program.cs +++ b/src/samples/Teams/ConversationBot/Program.cs @@ -4,7 +4,7 @@ using ConversationBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/samples/Teams/ConversationBot/appManifest/manifest.json b/src/samples/Teams/ConversationBot/appManifest/manifest.json index b5a3d0a5..813b441d 100644 --- a/src/samples/Teams/ConversationBot/appManifest/manifest.json +++ b/src/samples/Teams/ConversationBot/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.14", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.conversations", + "packageName": "com.Microsoft.Agents.Extensions.Teams.conversations", "developer": { "name": "TeamsConversationBot", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/LinkUnfurling/AppManifest/manifest.json b/src/samples/Teams/LinkUnfurling/AppManifest/manifest.json index c3aa01aa..35b80bd8 100644 --- a/src/samples/Teams/LinkUnfurling/AppManifest/manifest.json +++ b/src/samples/Teams/LinkUnfurling/AppManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.linkunfurling", + "packageName": "com.Microsoft.Agents.Extensions.Teams.linkunfurling", "developer": { "name": "Link Unfurling", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs index dc7505fb..acdedda4 100644 --- a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs +++ b/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs @@ -8,8 +8,8 @@ using AdaptiveCards; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Models; namespace LinkUnfurling.Bots { diff --git a/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj b/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj index 7d17b9c9..46924ef1 100644 --- a/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj +++ b/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/samples/Teams/LinkUnfurling/Program.cs b/src/samples/Teams/LinkUnfurling/Program.cs index ba788c27..e9eede15 100644 --- a/src/samples/Teams/LinkUnfurling/Program.cs +++ b/src/samples/Teams/LinkUnfurling/Program.cs @@ -4,7 +4,7 @@ using LinkUnfurling.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs b/src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs index e1cd4b12..45903d83 100644 --- a/src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs +++ b/src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams.Connector; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; using System.Threading; diff --git a/src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj b/src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj index 7612a20c..d975ac5f 100644 --- a/src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj +++ b/src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs index b68a6ee4..9248774b 100644 --- a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs +++ b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs @@ -13,11 +13,11 @@ using System.Threading.Tasks; using System.Web; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams; +using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Connector; using Microsoft.Agents.BotBuilder; diff --git a/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj b/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj index bb90abac..a5a8a37a 100644 --- a/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj +++ b/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/samples/Teams/Meetings-Notification/Program.cs b/src/samples/Teams/Meetings-Notification/Program.cs index 5ba88922..07f60950 100644 --- a/src/samples/Teams/Meetings-Notification/Program.cs +++ b/src/samples/Teams/Meetings-Notification/Program.cs @@ -4,7 +4,7 @@ using InMeetingNotificationsBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs index 06086418..b324be06 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs @@ -7,13 +7,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Extensions.Configuration; using System.Net.Http; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; diff --git a/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj b/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj index 205a6ae3..6002daab 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj +++ b/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/samples/Teams/MessagingExtensionsSearch/Program.cs b/src/samples/Teams/MessagingExtensionsSearch/Program.cs index fdef9940..3cd899f7 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Program.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Program.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore; using MessagingExtensionsSearch.Bots; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json b/src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json index d33ee3fc..b6446482 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json +++ b/src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.14", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.messageextension", + "packageName": "com.Microsoft.Agents.Extensions.Teams.messageextension", "developer": { "name": "TeamsMessagingExtensionsSearch", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs index e25429a2..965198be 100644 --- a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs +++ b/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs @@ -8,11 +8,11 @@ using System.Threading.Tasks; using AdaptiveCards; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Extensions.Configuration; using System.Text.Json.Nodes; using TaskModule.Models; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; namespace TaskModule.Bots diff --git a/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs b/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs index 2f5ac7b8..44e32dd9 100644 --- a/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs +++ b/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; namespace TaskModule.Models { diff --git a/src/samples/Teams/TaskModule/Program.cs b/src/samples/Teams/TaskModule/Program.cs index e3177919..e2f68cbc 100644 --- a/src/samples/Teams/TaskModule/Program.cs +++ b/src/samples/Teams/TaskModule/Program.cs @@ -3,7 +3,7 @@ using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/samples/Teams/TaskModule/TaskModule.csproj b/src/samples/Teams/TaskModule/TaskModule.csproj index 981861c1..a60b9a6e 100644 --- a/src/samples/Teams/TaskModule/TaskModule.csproj +++ b/src/samples/Teams/TaskModule/TaskModule.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/samples/Teams/TaskModule/appManifest/manifest.json b/src/samples/Teams/TaskModule/appManifest/manifest.json index d2e98830..8dd09262 100644 --- a/src/samples/Teams/TaskModule/appManifest/manifest.json +++ b/src/samples/Teams/TaskModule/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.taskmodule", + "packageName": "com.Microsoft.Agents.Extensions.Teams.taskmodule", "developer": { "name": "Microsoft", "websiteUrl": "https://example.azurewebsites.net", diff --git a/src/samples/Teams/bot-all-cards/appManifest/manifest.json b/src/samples/Teams/bot-all-cards/appManifest/manifest.json index af4d0576..348c0aa3 100644 --- a/src/samples/Teams/bot-all-cards/appManifest/manifest.json +++ b/src/samples/Teams/bot-all-cards/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.allbotcards", + "packageName": "com.Microsoft.Agents.Extensions.Teams.allbotcards", "developer": { "name": "Teams App, Inc.", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj b/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj index adea441a..91e7fd65 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj +++ b/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs index d8c1e8bd..46c59d48 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs @@ -7,7 +7,7 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index 07595030..ddd956e1 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -11,8 +11,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json b/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json index 924defdf..7a714c07 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json +++ b/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.conversationsso", + "packageName": "com.Microsoft.Agents.Extensions.Teams.conversationsso", "developer": { "name": "Teams App, Inc.", "websiteUrl": "https://example.azurewebsites.net", diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs index 31d81198..a41376d7 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs @@ -9,7 +9,7 @@ using AdaptiveCards.Templating; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; namespace PeoplePicker.Bots diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj b/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj index 6eb448e9..9351b594 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj +++ b/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs index 3faeeb4a..d8153812 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PeoplePicker.Bots; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json b/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json index 54a2459b..22464818 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json +++ b/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.peoplepicker", + "packageName": "com.Microsoft.Agents.Extensions.Teams.peoplepicker", "developer": { "name": "Microsoft", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs index 1db98009..9e3050dc 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; namespace ReceiveMessagesWithRSC.Bots diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs index 4bc74c9d..baf91f9c 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ReceiveMessagesWithRSC.Bots; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj b/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj index 5e90ce53..03bd831b 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json b/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json index 8c4ec8c2..e174126d 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.12", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.channelmessagesrsc", + "packageName": "com.Microsoft.Agents.Extensions.Teams.channelmessagesrsc", "developer": { "name": "Microsoft", "websiteUrl": "https://www.microsoft.com", diff --git a/src/samples/Teams/bot-request-approval/BotRequestApproval.csproj b/src/samples/Teams/bot-request-approval/BotRequestApproval.csproj index 621c588f..7194479c 100644 --- a/src/samples/Teams/bot-request-approval/BotRequestApproval.csproj +++ b/src/samples/Teams/bot-request-approval/BotRequestApproval.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs b/src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs index 45088430..32f59e2b 100644 --- a/src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs @@ -6,10 +6,10 @@ using BotRequestApproval.Models; using Microsoft.Agents.Core.Models; using System.Text.Json; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Teams.Connector; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Models; namespace Microsoft.Agents.Samples.Bots { diff --git a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs index b056d074..e7ec2bf6 100644 --- a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs index 25f8d273..39f6b142 100644 --- a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs +++ b/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs @@ -9,9 +9,9 @@ using AdaptiveCards.Templating; using Microsoft.Agents.BotBuilder.Dialogs; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams; -using Microsoft.Agents.Teams.Connector; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams; +using Microsoft.Agents.Extensions.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; diff --git a/src/samples/Teams/bot-tag-mention/Program.cs b/src/samples/Teams/bot-tag-mention/Program.cs index eb1126f7..785a31be 100644 --- a/src/samples/Teams/bot-tag-mention/Program.cs +++ b/src/samples/Teams/bot-tag-mention/Program.cs @@ -10,8 +10,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; diff --git a/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj b/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj index 27f37a54..6024b9c9 100644 --- a/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj +++ b/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json b/src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json index 79c75a4f..e15faeae 100644 --- a/src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json +++ b/src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json @@ -3,7 +3,7 @@ "manifestVersion": "1.16", "version": "1.0.0", "id": "<>", - "packageName": "com.microsoft.agents.teams.teamsauth", + "packageName": "com.Microsoft.Agents.Extensions.Teams.teamsauth", "developer": { "name": "Microsoft", "websiteUrl": "https://example.azurewebsites.net", diff --git a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs index e72f28f9..14010738 100644 --- a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs +++ b/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; using Microsoft.Agents.BotBuilder.Dialogs; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; diff --git a/src/samples/Teams/bot-teams-authentication/Program.cs b/src/samples/Teams/bot-teams-authentication/Program.cs index ca607321..200fa610 100644 --- a/src/samples/Teams/bot-teams-authentication/Program.cs +++ b/src/samples/Teams/bot-teams-authentication/Program.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Hosting; using TeamsAuth.Bots; using TeamsAuth.Dialogs; -using Microsoft.Agents.Teams; +using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); diff --git a/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj b/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj index db9d385f..6810d836 100644 --- a/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj +++ b/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs b/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs index c25c910c..7c5d031d 100644 --- a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs +++ b/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs @@ -13,9 +13,9 @@ using System; using System.Text.Json.Nodes; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Compat; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Connector; namespace TypeaheadSearch.Bots { diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj b/src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj index 44232683..4d055ab3 100644 --- a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj +++ b/src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj index e8bc8bed..cff3755e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs index 8f615510..115e8155 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs @@ -8,9 +8,9 @@ using System.Threading.Tasks; using Microsoft.Agents.Core.Models; using Xunit; -using Microsoft.Agents.SharePoint.Models; -using Microsoft.Agents.SharePoint; -using Microsoft.Agents.SharePoint.Compat; +using Microsoft.Agents.Extensions.SharePoint.Models; +using Microsoft.Agents.Extensions.SharePoint; +using Microsoft.Agents.Extensions.SharePoint.Compat; namespace Microsoft.Agents.BotBuilder.Tests.SharePoint { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs index 2622743c..7021d92f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/AppBasedLinkQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class AppBasedLinkQueryTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AttachmentExtensionsTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/AttachmentExtensionsTests.cs index 4acb86fe..81b19e2e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/AttachmentExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AttachmentExtensionsTests.cs @@ -4,10 +4,10 @@ using System.Collections; using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class AttachmentExtensionsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs similarity index 83% rename from src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs index 0d05d0ff..8f70abd0 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/BotConfigAuthTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class BotConfigAuthTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs index a0537bcd..78a76669 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/CacheInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class CacheInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs index c3006771..6d4a93ed 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ChannelInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ChannelInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs similarity index 83% rename from src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs index dab3d185..a14512e2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigAuthResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ConfigAuthResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs similarity index 81% rename from src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs index 06b83002..b9444d9d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ConfigResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs similarity index 83% rename from src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs index 702ba44d..eeaab5c5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConfigTaskResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ConfigTaskResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs index 2b435f2e..e84bf3c5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RestTeamsOperationsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Connector; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Moq; using System; using System.Collections.Generic; @@ -16,7 +16,7 @@ using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class RestTeamsOperationsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryActionTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryActionTests.cs index 66700102..b13d3364 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryActionTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Connector; using System; using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class RetryActionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryParamTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryParamTests.cs index f11d43fc..36d5e23f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/Connector/RetryParamTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RetryParamTests.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Connector; +using Microsoft.Agents.Extensions.Teams.Connector; using System; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class RetryParamTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs index dbedc8e9..073029fb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ConversationListTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ConversationListTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs index 4218c2dd..c116a0f5 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class FileConsentCardResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs index 0287fe60..f11182bb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileConsentCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class FileConsentCardTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs index 3750f640..0ffcb3ed 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileDownloadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class FileDownloadInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs index 94ae8a42..4622aaf2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileInfoCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class FileInfoCardTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs index 034431c7..5c5b980f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/FileUploadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class FileUploadInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs index 0334802d..67348874 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MeetingParticipantInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs index 1bf7ee0c..efa8fa9d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MeetingParticipantsEventDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MeetingParticipantsEventDetailsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs index d9e7a589..6f18c07f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAppTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadAppTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs index a6a75faf..9da3a56f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadAttachmentTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs index 99730621..075da79e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadBodyTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadBodyTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs index 67efa031..572aceb9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadConversationsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadConversationsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs index d718788d..4f84287f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadFromTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadFromTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs index 3d3a9ce6..37fe79c9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadMentionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadMentionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs index f58d4992..a182c8a9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadReactionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadReactionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs similarity index 98% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs index 42b45108..27189b8e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs @@ -5,10 +5,10 @@ using System.Collections.Generic; using System.Text.Json; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { /// /// Tests to ensure that MessageActionsPayload works as expected. diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs index fe11e46b..60ebae28 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessageActionsPayloadUserTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessageActionsPayloadUserTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs index 5f2fed01..8d00761a 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionActionResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs index e3d6ca43..fb04a088 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionActionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs index b53723fe..26d4aa33 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionAttachmentTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs index f98fe533..e441d252 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionParametersTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionParametersTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs index 92b7011e..36656190 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryOptionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionQueryOptionsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs index 4bbf2d3e..b09ab334 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionQueryTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs index 3b0738eb..d328d92d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs index 804955cb..d7e0dbfa 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionResultTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionResultTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs index 686b81c3..8220ebfd 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/MessagingExtensionSuggestedActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class MessagingExtensionSuggestedActionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj similarity index 79% rename from src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj index 40db90bb..ffb36205 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/Microsoft.Agents.Teams.Tests.csproj +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj @@ -23,7 +23,6 @@ - - + diff --git a/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs index c634a76c..dea53368 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/NotImplementedAdapter.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { internal class NotImplementedAdapter : ChannelAdapter { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs index cf8ed693..7c74a1ad 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/NotificationInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class NotificationInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs index 30cc7383..b2a03f43 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardActionBaseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs index be51d42d..22692aa8 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardActionCardTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs index 726f660f..5c6be84d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardActionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardActionQueryTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs index f595ba05..0a263da2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardDateInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardDateInputTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs index 3f7fc859..b0feec7c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardFactTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs @@ -1,10 +1,10 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardFactTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs index c9f14dae..5dc3755f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardHttpPOSTTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs index 454757ba..301e36c4 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardImageTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs @@ -1,10 +1,10 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardImageTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs index 6aaaece3..7f467b79 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardInputBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardInputBaseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs index bd075c28..4d499b00 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardMultichoiceInputChoiceTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs index 0819f8b5..4711d2c9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardMultichoiceInputTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs index e68095ba..d7129f64 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardOpenUriTargetTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs index ce9fe1b1..fa89f004 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardOpenUriTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardOpenUriTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs index 9710cb98..afa31d28 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardSectionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardSectionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs index 12e5d4f1..caa87d1b 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs index 0c74abde..b7631e34 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardTextInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs @@ -1,10 +1,10 @@ // Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardTextInputTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs index 2638ea8c..0e37eb97 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/O365ConnectorCardViewActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class O365ConnectorCardViewActionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs index 228ef45b..f429ae94 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/ReadReceiptInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class ReadReceiptInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs index 68386a41..e7fb4f42 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/SigninStateVerificationQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class SigninStateVerificationQueryTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs similarity index 98% rename from src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs index 986baeb4..b33db6bc 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/SimpleAdapter.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs @@ -10,7 +10,7 @@ using Microsoft.Agents.Core.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class SimpleAdapter : ChannelAdapter { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs new file mode 100644 index 00000000..9baa0721 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Extensions.Teams.Models; +using Xunit; + +namespace Microsoft.Agents.Extensions.Teams.Tests +{ + public class SurfaceTests + { + [Fact] + public void Surface_In_TargetedMeetingNotificationValue() + { + // + // Deserialize + // + + // TargetedMeetingNotificationValue json + // {"surfaces":[{"contentType":"task","surface":"meetingStage"},{"tabEntityId":"id","surface":"meetingTabIcon"}]} + var json = "{\"surfaces\":[{\"contentType\":\"task\",\"surface\":\"meetingStage\"},{\"tabEntityId\":\"id\",\"surface\":\"meetingTabIcon\"}]}"; + var json_compat = "{\"surfaces\": [{\"type\": \"MeetingStage\", \"contentType\": \"Task\"}, {\"type\": \"MeetingTabIcon\", \"tabEntityId\": \"id\"}]}"; + + // Act + var obj = ProtocolJsonSerializer.ToObject(json); + Assert.NotNull(obj); + + // first Surface + Assert.IsAssignableFrom>(obj.Surfaces[0]); + + // second Surface + Assert.IsAssignableFrom(obj.Surfaces[1]); + Assert.Equal("id", ((MeetingTabIconSurface)obj.Surfaces[1]).TabEntityId); + + // Again with compat variant + obj = ProtocolJsonSerializer.ToObject(json_compat); + Assert.NotNull(obj); + + // first Surface + Assert.IsAssignableFrom>(obj.Surfaces[0]); + + // second Surface + Assert.IsAssignableFrom(obj.Surfaces[1]); + Assert.Equal("id", ((MeetingTabIconSurface)obj.Surfaces[1]).TabEntityId); + + // + // Serialize + // + var to_json = ProtocolJsonSerializer.ToJson(obj); + Assert.Equal(json, to_json); + } + } +} diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs similarity index 84% rename from src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs index 02ab6c40..21d3d547 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabContextTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs similarity index 86% rename from src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs index 017f4da5..c2e7ad26 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabEntityContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabEntityContextTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs similarity index 83% rename from src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs index c6fd4d6c..800a82e7 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabRequestTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabRequestTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs similarity index 85% rename from src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs index 63f7abc6..8945128d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabResponseCardTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs similarity index 79% rename from src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs index 9122f5b2..055bdb99 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseCardsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System.Collections.Generic; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabResponseCardsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs similarity index 80% rename from src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs index 30ea65dd..267d7fb6 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponsePayloadTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabResponsePayloadTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs similarity index 77% rename from src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs index e91a5ef6..0af4f11d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs similarity index 85% rename from src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs index 5d391b15..effc597d 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitDataTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections; using System.Collections.Generic; using System.Text.Json; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabSubmitDataTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs similarity index 83% rename from src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs index 71ef4700..f08fc9d2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSubmitTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabSubmitTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs similarity index 81% rename from src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs index 30b7acee..29ee7401 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabSuggestedActionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TabSuggestedActionsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs similarity index 97% rename from src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs index 12c8cbb9..3125742f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TabsTestData.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs @@ -6,9 +6,9 @@ using System.Text.Json; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { internal class TabsTestData { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs index 5b10022e..eaf43758 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System.Text.Json; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleActionTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs index 52dce2d1..5fb9cf8e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleCardResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs index 4046c8ef..ac75723c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleContinueResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleContinueResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs index 8cad1dcd..95c5a28c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleMessageResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleMessageResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs index 3104d782..6dfd6593 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleRequestContextTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs index ef27ddb7..5107658c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleRequestTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleRequestTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs index d925a79f..26f552bb 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleResponseBaseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs index 6681b2f2..2b5515e9 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleResponseTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs index be18a489..92ca219f 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TaskModuleTaskInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs @@ -3,10 +3,10 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TaskModuleTaskInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs index 3b380647..0408a48e 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamDetailsTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs index c5a60258..c8d7a812 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs index 582f001b..977e39bc 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs @@ -6,12 +6,12 @@ using System.Text.Json; using System.Threading.Tasks; using Xunit; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Globalization; using Microsoft.Agents.BotBuilder; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsActivityHandlerTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs index 87ae54da..ac9fea39 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsChannelAccountTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs similarity index 69% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs index 1ae0c651..a1b2f0ba 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsChannelDataTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs @@ -6,10 +6,10 @@ using Microsoft.Agents.Core; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsChannelDataTests { @@ -25,7 +25,7 @@ public void TeamsChannelDataInits() var settings = new TeamsChannelDataSettings(channel); var onBehalfOf = new List() { - new OnBehalfOf() + new OnBehalfOf() { DisplayName = "onBehalfOfTest", ItemId = 0, @@ -65,7 +65,7 @@ public void GetAadGroupId() { // Arrange const string AadGroupId = "teamGroup123"; - var activity = (IActivity) new Activity { ChannelData = new TeamsChannelData { Team = new TeamInfo { AadGroupId = AadGroupId } } }; + var activity = (IActivity)new Activity { ChannelData = new TeamsChannelData { Team = new TeamInfo { AadGroupId = AadGroupId } } }; // Act var channelData = activity.GetChannelData(); @@ -91,5 +91,25 @@ public void AdditionalProperties_ExtraChannelDataFields() Assert.True(asTeamsChannelData.Properties.ContainsKey(TestKey)); Assert.Equal(TestValue, asTeamsChannelData.Properties[TestKey].ToString()); } + + [Fact] + public void Activity_TeamsChannelData() + { + const string TestKey = "thekey"; + const string TestValue = "the test value"; + + var json = "{\"type\": \"message\", \"channelData\": {\"tenant\": {\"id\":\"tenantid\"}, \"meeting\": {\"id\":\"meetingid\"}, \"team\": {\"id\": \"id\", \"name\": \"name\", \"aadGroupId\": \"aadGroupId\"}, \"thekey\": \"the test value\"}}"; + var activity = ProtocolJsonSerializer.ToObject(json); + var channelData = activity.GetChannelData(); + + Assert.IsType(channelData); + Assert.Equal("id", channelData.Team.Id); + Assert.Equal("name", channelData.Team.Name); + Assert.Equal("aadGroupId", channelData.Team.AadGroupId); + Assert.Equal("meetingid", channelData.Meeting.Id); + Assert.Equal("tenantid", channelData.Tenant.Id); + Assert.True(channelData.Properties.ContainsKey(TestKey)); + Assert.Equal(TestValue, channelData.Properties[TestKey].ToString()); + } } } diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs index 98542890..93ea8288 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsMeetingInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs index f17132a1..ed516fa2 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsMeetingParticipantTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsMeetingParticipantTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs index 66752b40..8338111c 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsPagedMembersResultTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsPagedMembersResultTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs index 042e878a..c71355d1 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TeamsParticipantChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TeamsParticipantChannelAccountTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs index 2711d476..aeb058ac 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TenantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { public class TenantInfoTests { diff --git a/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TestActivityHandler.cs similarity index 99% rename from src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/TestActivityHandler.cs index 4edb17cf..481958f6 100644 --- a/src/tests/Microsoft.Agents.Teams.Tests/TestActivityHandler.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TestActivityHandler.cs @@ -3,15 +3,15 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Teams.Compat; -using Microsoft.Agents.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.Extensions.Teams.Models; using System.Collections.Generic; using System.Reflection; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests { internal class TestActivityHandler : TeamsActivityHandler { diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs index cf337676..09567ab7 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/ServiceCollectionExtensionsTests.cs @@ -54,6 +54,7 @@ public void AddBot_ShouldSetServices() var expected = new List{ typeof(ConfigurationConnections), typeof(RestChannelServiceClientFactory), + typeof(MemoryStorage), // CloudAdapter services. typeof(HostedActivityService), typeof(HostedTaskService), @@ -81,7 +82,6 @@ public void AddChannelHost_ShouldSetServices() var expected = new List{ typeof(IChannelHost), typeof(IChannelFactory), - typeof(MemoryStorage), typeof(ConversationIdFactory), typeof(IChannelApiHandler), // Type passed to AddChannelHost. typeof(IChannelApiHandler) From a871613516b2bae52457d178f04e5646f363b296 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 7 Feb 2025 20:04:26 -0600 Subject: [PATCH 19/60] Doc comments in State --- .../App/Application.cs | 1 - .../State/BotState.cs | 110 ++++-------------- .../State/ConversationState.cs | 3 - .../State/IBotState.cs | 97 ++++++++++++++- .../State/ITurnState.cs | 60 ++++++++++ .../State/PrivateConversationState.cs | 3 - .../State/UserState.cs | 3 - 7 files changed, 173 insertions(+), 104 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index bf947b1c..72efd840 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -4,7 +4,6 @@ using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Storage; using System; using System.Collections.Concurrent; using System.Text.RegularExpressions; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs index d1b116d2..ab4c1a64 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs @@ -13,18 +13,8 @@ namespace Microsoft.Agents.BotBuilder.State { /// - /// Defines a state management object and automates the reading and writing of associated state - /// properties to a storage layer. + /// Base class for BotState key/value state. /// - /// - /// Each state management object defines a scope for a storage layer. - /// - /// State properties are created within a state management scope, and the Agents SDK - /// defines these scopes: - /// , , and . - /// - /// You can define additional scopes for your bot. - /// /// public abstract class BotState : IPropertyManager, IBotState { @@ -50,6 +40,7 @@ public BotState(IStorage storage, string stateName) Name = stateName ?? throw new ArgumentNullException(nameof(stateName)); } + /// public string Name { get; private set; } /// @@ -67,6 +58,7 @@ public IStatePropertyAccessor CreateProperty(string name) return new BotStatePropertyAccessor(this, name); } + /// public bool HasValue(string name) { if (!IsLoaded()) @@ -78,11 +70,7 @@ public bool HasValue(string name) return cachedState.State.ContainsKey(name); } - /// - /// Delete the property. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// A representing the asynchronous operation. + /// public void DeleteValue(string name) { if (!IsLoaded()) @@ -93,13 +81,7 @@ public void DeleteValue(string name) DeletePropertyValue(name); } - /// - /// Get the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// Defines the default value. - /// Invoked when no value been set for the requested state property. - /// If defaultValueFactory is defined as null in that case, the method returns null and + /// public T GetValue(string name, Func defaultValueFactory = null) { if (!IsLoaded()) @@ -135,12 +117,7 @@ public T GetValue(string name, Func defaultValueFactory = null) return result; } - /// - /// Set the property value. The semantics are intended to be lazy, note the use of LoadAsync at the start. - /// - /// value. - /// value. - /// A representing the asynchronous operation. + /// public void SetValue(string name, T value) { if (!IsLoaded()) @@ -154,25 +131,12 @@ public void SetValue(string name, T value) /// /// True if state has been loaded. /// - /// public bool IsLoaded() { return _cachedBotState != null; } - /// - /// Populates the state cache for this from the storage layer. - /// - /// - /// LoadAsync loads State for the specified turn. - /// - /// The context object for this turn. - /// Optional, true to overwrite any existing state cache; - /// or false to load state from storage only if the cache doesn't already exist. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. + /// public virtual async Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(turnContext); @@ -212,16 +176,7 @@ private bool ShouldLoad(ITurnContext turnContext, string storageKey, bool force) return force || _cachedBotState == null || _cachedBotState.State == null; } - /// - /// Writes the state cache for this to the storage layer. - /// - /// The context object for this turn. - /// Optional, true to save the state cache to storage; - /// or false to save state to storage only if a property in the cache has changed. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. + /// public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(turnContext); @@ -240,14 +195,7 @@ public virtual async Task SaveChangesAsync(ITurnContext turnContext, bool force } } - /// - /// Clears the state cache for this . - /// - /// A task that represents the work queued to execute. - /// This method clears the state cache in the turn context. Call - /// to persist this - /// change in the storage layer. - /// + /// public virtual void ClearState() { if (!IsLoaded()) @@ -259,14 +207,7 @@ public virtual void ClearState() GetCachedState().Clear(); } - /// - /// Deletes any state in storage and the cache for this . - /// - /// The context object for this turn. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - /// is null. + /// public virtual async Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { if (IsLoaded()) @@ -278,26 +219,6 @@ public virtual async Task DeleteStateAsync(ITurnContext turnContext, Cancellatio await _storage.DeleteAsync(new[] { storageKey }, cancellationToken).ConfigureAwait(false); } - /// - /// Gets a copy of the raw cached data for this from the turn context. - /// - /// A JSON representation of the cached state. - internal JsonElement Get() - { - var cachedState = GetCachedState(); - return JsonSerializer.SerializeToElement(cachedState.State, ProtocolJsonSerializer.SerializationOptions); - } - - /// - /// Gets the cached bot state instance that wraps the raw cached data for this - /// from the turn context. - /// - /// The cached bot state instance. - internal CachedBotState GetCachedState() - { - return _cachedBotState; - } - /// /// When overridden in a derived class, gets the key to use when reading and writing state to and from storage. /// @@ -378,6 +299,17 @@ protected void SetPropertyValue(string propertyName, object value) cachedState.State[propertyName] = value; } + internal JsonElement Get() + { + var cachedState = GetCachedState(); + return JsonSerializer.SerializeToElement(cachedState.State, ProtocolJsonSerializer.SerializationOptions); + } + + internal CachedBotState GetCachedState() + { + return _cachedBotState; + } + /// /// Internal cached bot state. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs index 59b3a9ed..ac0b1e52 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ConversationState.cs @@ -12,9 +12,6 @@ namespace Microsoft.Agents.BotBuilder.State /// /// Conversation state is available in any turn in a specific conversation, regardless of user, /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. /// /// The storage layer to use. public class ConversationState(IStorage storage) : BotState(storage, ScopeName) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs index 88ae4aab..dbdd02cc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/IBotState.cs @@ -7,18 +7,105 @@ namespace Microsoft.Agents.BotBuilder.State { + /// + /// Defines a state management object and automates the reading and writing of associated state + /// properties to a storage layer. + /// + /// + /// Each state management object defines a scope for a storage layer. + /// + /// State properties are created within a state management scope, and the Agents SDK + /// defines these scopes: + /// , , and . + /// + /// You can define additional scopes for your bot. + /// + /// public interface IBotState { + /// + /// The scope name of the state. + /// string Name { get; } - void ClearState(); - Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); - void DeleteValue(string name); + /// + /// Get a property value. + /// + /// The name of the property. + /// Defines the default value. + /// Invoked when no value been set for the requested state property. + /// If defaultValueFactory is defined as null in that case, the method returns null. T GetValue(string name, Func defaultValueFactory = null); + + /// + /// Set a property value. + /// + /// The name of the property. + /// The property value. + void SetValue(string name, T value); + + /// + /// Checks for the existence of a property. + /// + /// The name of the property. + bool HasValue(string name); + + /// + /// Delete a property. + /// + /// The name of the property. + void DeleteValue(string name); + + /// + /// True if state has been loaded. + /// bool IsLoaded(); + + /// + /// Clears the state. + /// + /// A task that represents the work queued to execute. + /// This method clears the state cache. Call + /// to persist this + /// change in the storage layer. + /// + void ClearState(); + + /// + /// Populates state from the storage layer. + /// + /// + /// LoadAsync loads State for the specified turn. + /// + /// The context object for this turn. + /// Optional, true to overwrite any existing state cache; + /// or false to load state from storage only if the cache doesn't already exist. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. Task LoadAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + + /// + /// Writes state to the storage layer. + /// + /// The context object for this turn. + /// Optional, true to save the state cache to storage; + /// or false to save state to storage only if a property in the cache has changed. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. Task SaveChangesAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); - void SetValue(string name, T value); - bool HasValue(string name); + + /// + /// Deletes state in storage. + /// + /// The context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. + Task DeleteStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs index d4daf607..c91dcb10 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/ITurnState.cs @@ -7,6 +7,9 @@ namespace Microsoft.Agents.BotBuilder.State { + /// + /// TurnState represents the state for a bot. State is composed of 1+ state scopes. + /// public interface ITurnState { ConversationState Conversation { get; } @@ -17,13 +20,70 @@ public interface ITurnState IBotState GetScope(string scope); T GetScope(); + /// + /// Get a property value. + /// + /// The full path to the property: {scope}.{name} + /// Defines the default value. + /// Invoked when no value been set for the requested state property. + /// If defaultValueFactory is defined as null in that case, the method returns null. T GetValue(string path, Func defaultValueFactory = null); + + /// + /// Set a property value. + /// + /// The full path to the property: {scope}.{name} + /// The property value. void SetValue(string path, object value); + + /// + /// Delete a property. + /// + /// The full path to the property: {scope}.{name} void DeleteValue(string path); + + /// + /// Checks for the existence of a property. + /// + /// The full path to the property: {scope}.{name} bool HasValue(string path); + /// + /// Clears the state. + /// + /// The scope name. eg "conversation", etc... + /// A task that represents the work queued to execute. + /// This method clears the state cache. Call + /// to persist this + /// change in the storage layer. + /// void ClearState(string scope); + + /// + /// Populates all states from the storage layer. + /// + /// + /// LoadAsync loads State for the specified turn. + /// + /// The context object for this turn. + /// Optional, true to overwrite any existing state cache; + /// or false to load state from storage only if the cache doesn't already exist. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. Task LoadStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); + + /// + /// Writes all states to the storage layer. + /// + /// The context object for this turn. + /// Optional, true to save the state cache to storage; + /// or false to save state to storage only if a property in the cache has changed. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// is null. Task SaveStateAsync(ITurnContext turnContext, bool force = false, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs index 2dc9fa9e..fdd2b176 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/PrivateConversationState.cs @@ -12,9 +12,6 @@ namespace Microsoft.Agents.BotBuilder.State /// /// Conversation state is available in any turn in a specific conversation, regardless of user, /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. /// /// The storage layer to use. public class PrivateConversationState(IStorage storage) : BotState(storage, ScopeName) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs index 7daa6cc0..c0893e9b 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/UserState.cs @@ -12,9 +12,6 @@ namespace Microsoft.Agents.BotBuilder.State /// /// Conversation state is available in any turn in a specific conversation, regardless of user, /// such as in a group conversation. - /// - /// This implementation should NOT be used as a singleton. This includes registering as singleton - /// in DI. /// /// The storage layer to use. public class UserState(IStorage storage) : BotState(storage, ScopeName) From e18663f6c1e21aca4ca0b4115bc960f542ebbc62 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 09:41:35 -0600 Subject: [PATCH 20/60] Minor unit test reorg --- src/Microsoft.Agents.SDK.sln | 7 + .../App/State/IMemory.cs | 2 +- .../App/State/ITurnState.cs | 2 +- .../App/State/MemoryFork.cs | 2 +- .../App/State/Record.cs | 2 +- .../App/State/TempState.cs | 2 +- .../App/State/TurnState.cs | 2 +- .../App/State/TurnStateEntry.cs | 3 +- .../Compat/AutoSaveStateMiddleware.cs | 33 +- .../Adapters/MockUserTokenClient.cs | 303 ++++++++++++++++++ .../Adapters}/SimpleAdapter.cs | 2 +- .../Adapters/TestAdapter.cs | 261 +-------------- .../App/Command.cs | 113 +++++++ .../{ => Handler}/ActivityHandlerTests.cs | 7 +- .../Handler/Command.cs | 107 +++++++ .../Microsoft.Agents.BotBuilder.Tests.csproj | 1 - .../OAuthFlowTests.cs | 1 + ....Agents.Extensions.SharePoint.Tests.csproj | 29 ++ .../SharePointActivityHandlerTests.cs | 4 +- ...osoft.Agents.Extensions.Teams.Tests.csproj | 1 + .../{ => Model}/AppBasedLinkQueryTests.cs | 2 +- .../{ => Model}/BotConfigAuthTests.cs | 2 +- .../{ => Model}/CacheInfoTests.cs | 4 +- .../{ => Model}/ChannelInfoTests.cs | 2 +- .../{ => Model}/ConfigAuthResponseTests.cs | 2 +- .../{ => Model}/ConfigResponseTests.cs | 2 +- .../{ => Model}/ConfigTaskResponseTests.cs | 2 +- .../{ => Model}/ConversationListTests.cs | 4 +- .../FileConsentCardResponseTests.cs | 2 +- .../{ => Model}/FileConsentCardTests.cs | 4 +- .../{ => Model}/FileDownloadInfoTests.cs | 4 +- .../{ => Model}/FileInfoCardTests.cs | 4 +- .../{ => Model}/FileUploadInfoTests.cs | 4 +- .../MeetingParticipantInfoTests.cs | 4 +- .../MeetingParticipantsEventDetailsTests.cs | 2 +- .../MessageActionsPayloadAppTests.cs | 4 +- .../MessageActionsPayloadAttachmentTests.cs | 4 +- .../MessageActionsPayloadBodyTests.cs | 4 +- ...MessageActionsPayloadConversationsTests.cs | 2 +- .../MessageActionsPayloadFromTests.cs | 4 +- .../MessageActionsPayloadMentionTests.cs | 4 +- .../MessageActionsPayloadReactionTests.cs | 4 +- .../{ => Model}/MessageActionsPayloadTests.cs | 2 +- .../MessageActionsPayloadUserTests.cs | 4 +- .../MessagingExtensionActionResponseTests.cs | 6 +- .../MessagingExtensionActionTests.cs | 6 +- .../MessagingExtensionAttachmentTests.cs | 2 +- .../MessagingExtensionParametersTests.cs | 4 +- .../MessagingExtensionQueryOptionsTests.cs | 4 +- .../MessagingExtensionQueryTests.cs | 4 +- .../MessagingExtensionResponseTests.cs | 6 +- .../MessagingExtensionResultTests.cs | 6 +- .../MessagingExtensionSuggestedActionTests.cs | 6 +- .../{ => Model}/NotImplementedAdapter.cs | 2 +- .../{ => Model}/NotificationInfoTests.cs | 4 +- .../O365ConnectorCardActionBaseTests.cs | 4 +- .../O365ConnectorCardActionCardTests.cs | 4 +- .../O365ConnectorCardActionQueryTests.cs | 4 +- .../O365ConnectorCardDateInputTests.cs | 4 +- .../{ => Model}/O365ConnectorCardFactTests.cs | 4 +- .../O365ConnectorCardHttpPOSTTests.cs | 4 +- .../O365ConnectorCardImageTests.cs | 4 +- .../O365ConnectorCardInputBaseTests.cs | 4 +- ...onnectorCardMultichoiceInputChoiceTests.cs | 4 +- .../O365ConnectorCardMultichoiceInputTests.cs | 8 +- .../O365ConnectorCardOpenUriTargetTests.cs | 4 +- .../O365ConnectorCardOpenUriTests.cs | 4 +- .../O365ConnectorCardSectionTests.cs | 8 +- .../{ => Model}/O365ConnectorCardTests.cs | 4 +- .../O365ConnectorCardTextInputTests.cs | 4 +- .../O365ConnectorCardViewActionTests.cs | 4 +- .../{ => Model}/ReadReceiptInfoTests.cs | 2 +- .../SigninStateVerificationQueryTests.cs | 6 +- .../{ => Model}/SurfaceTests.cs | 2 +- .../{ => Model}/TabContextTests.cs | 2 +- .../{ => Model}/TabEntityContextTests.cs | 2 +- .../{ => Model}/TabRequestTests.cs | 4 +- .../{ => Model}/TabResponseCardTests.cs | 2 +- .../{ => Model}/TabResponseCardsTests.cs | 4 +- .../{ => Model}/TabResponsePayloadTests.cs | 4 +- .../{ => Model}/TabResponseTests.cs | 4 +- .../{ => Model}/TabSubmitDataTests.cs | 4 +- .../{ => Model}/TabSubmitTests.cs | 4 +- .../{ => Model}/TabSuggestedActionsTests.cs | 4 +- .../{ => Model}/TabsTestData.cs | 12 +- .../{ => Model}/TaskModuleActionTests.cs | 4 +- .../TaskModuleCardResponseTests.cs | 4 +- .../TaskModuleContinueResponseTests.cs | 4 +- .../TaskModuleMessageResponseTests.cs | 4 +- .../TaskModuleRequestContextTests.cs | 4 +- .../{ => Model}/TaskModuleRequestTests.cs | 4 +- .../TaskModuleResponseBaseTests.cs | 4 +- .../{ => Model}/TaskModuleResponseTests.cs | 6 +- .../{ => Model}/TaskModuleTaskInfoTests.cs | 6 +- .../{ => Model}/TeamDetailsTests.cs | 4 +- .../{ => Model}/TeamInfoTests.cs | 2 +- .../{ => Model}/TeamsActivityHandlerTests.cs | 3 +- .../{ => Model}/TeamsChannelAccountTests.cs | 6 +- .../{ => Model}/TeamsChannelDataTests.cs | 2 +- .../{ => Model}/TeamsMeetingInfoTests.cs | 4 +- .../TeamsMeetingParticipantTests.cs | 4 +- .../TeamsPagedMembersResultTests.cs | 4 +- .../TeamsParticipantChannelAccountTests.cs | 4 +- .../{ => Model}/TenantInfoTests.cs | 6 +- .../SimpleAdapter.cs | 78 ----- 105 files changed, 759 insertions(+), 536 deletions(-) create mode 100644 src/tests/BotBuilder.Testing/Adapters/MockUserTokenClient.cs rename src/tests/{Microsoft.Agents.BotBuilder.Tests => BotBuilder.Testing/Adapters}/SimpleAdapter.cs (98%) create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs rename src/tests/Microsoft.Agents.BotBuilder.Tests/{ => Handler}/ActivityHandlerTests.cs (99%) create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/Command.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/Microsoft.Agents.Extensions.SharePoint.Tests.csproj rename src/tests/{Microsoft.Agents.BotBuilder.Tests/SharePoint => Microsoft.Agents.Extensions.SharePoint.Tests}/SharePointActivityHandlerTests.cs (98%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/AppBasedLinkQueryTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/BotConfigAuthTests.cs (90%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/CacheInfoTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ChannelInfoTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ConfigAuthResponseTests.cs (90%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ConfigResponseTests.cs (89%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ConfigTaskResponseTests.cs (90%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ConversationListTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/FileConsentCardResponseTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/FileConsentCardTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/FileDownloadInfoTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/FileInfoCardTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/FileUploadInfoTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MeetingParticipantInfoTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MeetingParticipantsEventDetailsTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadAppTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadAttachmentTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadBodyTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadConversationsTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadFromTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadMentionTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadReactionTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadTests.cs (99%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessageActionsPayloadUserTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionActionResponseTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionActionTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionAttachmentTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionParametersTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionQueryOptionsTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionQueryTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionResponseTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionResultTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/MessagingExtensionSuggestedActionTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/NotImplementedAdapter.cs (88%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/NotificationInfoTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardActionBaseTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardActionCardTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardActionQueryTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardDateInputTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardFactTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardHttpPOSTTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardImageTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardInputBaseTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardMultichoiceInputChoiceTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardMultichoiceInputTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardOpenUriTargetTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardOpenUriTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardSectionTests.cs (97%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardTextInputTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/O365ConnectorCardViewActionTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/ReadReceiptInfoTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/SigninStateVerificationQueryTests.cs (92%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/SurfaceTests.cs (97%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabContextTests.cs (91%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabEntityContextTests.cs (92%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabRequestTests.cs (87%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabResponseCardTests.cs (91%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabResponseCardsTests.cs (84%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabResponsePayloadTests.cs (85%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabResponseTests.cs (82%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabSubmitDataTests.cs (88%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabSubmitTests.cs (87%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabSuggestedActionsTests.cs (85%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TabsTestData.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleActionTests.cs (89%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleCardResponseTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleContinueResponseTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleMessageResponseTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleRequestContextTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleRequestTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleResponseBaseTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleResponseTests.cs (94%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TaskModuleTaskInfoTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamDetailsTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamInfoTests.cs (93%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsActivityHandlerTests.cs (99%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsChannelAccountTests.cs (97%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsChannelDataTests.cs (98%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsMeetingInfoTests.cs (92%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsMeetingParticipantTests.cs (95%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsPagedMembersResultTests.cs (96%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TeamsParticipantChannelAccountTests.cs (97%) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Model}/TenantInfoTests.cs (91%) delete mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 5e2ddbb7..a443a04b 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -144,6 +144,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{2E783725-43EE-4DF5-8379-745EEF2DCDA0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.SharePoint.Tests", "tests\Microsoft.Agents.Extensions.SharePoint.Tests\Microsoft.Agents.Extensions.SharePoint.Tests.csproj", "{81AE589F-77BC-4873-B006-6EE5094E99D7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -370,6 +372,10 @@ Global {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E783725-43EE-4DF5-8379-745EEF2DCDA0}.Release|Any CPU.Build.0 = Release|Any CPU + {81AE589F-77BC-4873-B006-6EE5094E99D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81AE589F-77BC-4873-B006-6EE5094E99D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81AE589F-77BC-4873-B006-6EE5094E99D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81AE589F-77BC-4873-B006-6EE5094E99D7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -441,6 +447,7 @@ Global {790020D4-42D2-40EB-87F0-9DC2A1E6C01D} = {32CF12ED-B87D-4A08-9D24-DBAD9DD4D1FD} {B748D33C-AC75-4AEE-9305-34D1A0126202} = {674A812C-7287-4883-97F9-697D83750648} {2E783725-43EE-4DF5-8379-745EEF2DCDA0} = {B748D33C-AC75-4AEE-9305-34D1A0126202} + {81AE589F-77BC-4873-B006-6EE5094E99D7} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs index 19d314f9..7ddf6f22 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// Represents a memory, a key-value store that can be used to store and retrieve values. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs index 4b88b75f..9185ebb8 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// The turn state interface. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs index 48a887c4..bd75f135 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs index 0be910f5..aa40732a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// The class representing a record. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs index 321cbf8d..a80b1b5d 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// Temporary state. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs index 0817feb9..0e5a3ad1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// Base class defining a collection of turn state scopes. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs index ecaf91d6..650d2cf3 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Newtonsoft.Json.Linq; using System; using System.Text.Json; -namespace Microsoft.Agents.BotBuilder.Application.State +namespace Microsoft.Agents.BotBuilder.App.State { /// /// Accessor class for managing an individual state scope. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs index 71e7e88f..90adc934 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs @@ -12,7 +12,7 @@ namespace Microsoft.Agents.BotBuilder.Compat /// Middleware to automatically persist state before the end of each turn. /// /// - /// This calls + /// This calls /// on each state object it manages. /// public class AutoSaveStateMiddleware : IMiddleware @@ -23,10 +23,10 @@ public class AutoSaveStateMiddleware : IMiddleware /// Initializes a new instance of the class. /// /// initial list of objects to manage. - public AutoSaveStateMiddleware(params BotState[] botStates) + public AutoSaveStateMiddleware(params IBotState[] botStates) { _autoLoad = false; - BotStateSet = new TurnState(botStates); + TurnState = new TurnState(botStates); } /// @@ -34,10 +34,10 @@ public AutoSaveStateMiddleware(params BotState[] botStates) /// /// /// - public AutoSaveStateMiddleware(bool autoLoad, params BotState[] botStates) + public AutoSaveStateMiddleware(bool autoLoad, params IBotState[] botStates) { _autoLoad = autoLoad; - BotStateSet = new TurnState(botStates); + TurnState = new TurnState(botStates); } /// @@ -45,29 +45,16 @@ public AutoSaveStateMiddleware(bool autoLoad, params BotState[] botStates) /// a list of state management objects managed by this object. /// /// The state management objects managed by this object. - public AutoSaveStateMiddleware(TurnState botStateSet) + public AutoSaveStateMiddleware(ITurnState botStateSet) { - BotStateSet = botStateSet; + TurnState = botStateSet; } /// /// Gets or sets the list of state management objects managed by this object. /// /// The state management objects managed by this object. - public TurnState BotStateSet { get; set; } - - /// - /// Adds a state management object to the list of states to manage. - /// - /// The bot state to add. - /// The updated object. - public AutoSaveStateMiddleware Add(BotState botState) - { - ArgumentNullException.ThrowIfNull(botState); - - BotStateSet.Add(botState); - return this; - } + public ITurnState TurnState { get; set; } /// /// Before the turn ends, calls @@ -84,13 +71,13 @@ public AutoSaveStateMiddleware Add(BotState botState) // before turn if (_autoLoad) { - await BotStateSet.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); + await TurnState.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); } await next(cancellationToken).ConfigureAwait(false); // after turn - await BotStateSet.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + await TurnState.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/tests/BotBuilder.Testing/Adapters/MockUserTokenClient.cs b/src/tests/BotBuilder.Testing/Adapters/MockUserTokenClient.cs new file mode 100644 index 00000000..e1394d3c --- /dev/null +++ b/src/tests/BotBuilder.Testing/Adapters/MockUserTokenClient.cs @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Connector; +using Microsoft.Agents.Core.Models; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using System; +using System.Linq; + +namespace Microsoft.Agents.BotBuilder.Testing.Adapters +{ + internal class MockUserTokenClient : IUserTokenClient + { + private const string ExceptionExpected = "ExceptionExpected"; + private readonly IDictionary _userTokens = new Dictionary(); + private readonly IDictionary _exchangableToken = new Dictionary(); + private readonly IList _magicCodes = new List(); + + /// + /// Adds a fake user token so it can later be retrieved. + /// + /// The connection name. + /// The channel ID. + /// The user ID. + /// The token to store. + /// The optional magic code to associate with this token. + public void AddUserToken(string connectionName, string channelId, string userId, string token, string magicCode = null) + { + var key = new UserTokenKey() + { + ConnectionName = connectionName, + ChannelId = channelId, + UserId = userId, + }; + + if (magicCode == null) + { + if (_userTokens.ContainsKey(key)) + { + _userTokens[key] = token; + } + else + { + _userTokens.Add(key, token); + } + } + else + { + _magicCodes.Add(new TokenMagicCode() + { + Key = key, + MagicCode = magicCode, + UserToken = token, + }); + } + } + + /// + /// Adds a fake exchangeable token so it can be exchanged later. + /// + /// The connection name. + /// The channel ID. + /// The user ID. + /// The exchangeable token or resource URI. + /// The token to store. + public void AddExchangeableToken(string connectionName, string channelId, string userId, string exchangableItem, string token) + { + var key = new ExchangableTokenKey() + { + ConnectionName = connectionName, + ChannelId = channelId, + UserId = userId, + ExchangableItem = exchangableItem + }; + + if (_exchangableToken.ContainsKey(key)) + { + _exchangableToken[key] = token; + } + else + { + _exchangableToken.Add(key, token); + } + } + + /// Adds an instruction to throw an exception during exchange requests. + /// + /// The connection name. + /// The channel ID. + /// The user ID. + /// The exchangeable token or resource URI. + public void ThrowOnExchangeRequest(string connectionName, string channelId, string userId, string exchangableItem) + { + var key = new ExchangableTokenKey() + { + ConnectionName = connectionName, + ChannelId = channelId, + UserId = userId, + ExchangableItem = exchangableItem + }; + + if (_exchangableToken.ContainsKey(key)) + { + _exchangableToken[key] = ExceptionExpected; + } + else + { + _exchangableToken.Add(key, ExceptionExpected); + } + } + + public Task GetUserTokenAsync(string userId, string connectionName, string channelId, string magicCode, CancellationToken cancellationToken) + { + var key = new UserTokenKey() + { + ConnectionName = connectionName, + ChannelId = channelId, //turnContext.Activity.ChannelId, + UserId = userId //turnContext.Activity.From.Id, + }; + + if (magicCode != null) + { + var magicCodeRecord = _magicCodes.FirstOrDefault(x => key.Equals(x.Key)); + if (magicCodeRecord != null && magicCodeRecord.MagicCode == magicCode) + { + // move the token to long term dictionary + AddUserToken(connectionName, key.ChannelId, key.UserId, magicCodeRecord.UserToken); + _magicCodes.Remove(magicCodeRecord); + } + } + + if (_userTokens.TryGetValue(key, out string token)) + { + // found + return Task.FromResult(new TokenResponse() + { + ConnectionName = connectionName, + Token = token, + }); + } + + // not found + return Task.FromResult(null); + + } + + public Task GetSignInResourceAsync(string connectionName, IActivity activity, string finalRedirect, CancellationToken cancellationToken) + { + return Task.FromResult(new SignInResource() + { + SignInLink = $"https://fake.com/oauthsignin/{connectionName}/{activity.ChannelId}/{activity?.Recipient?.Id}", + TokenExchangeResource = new TokenExchangeResource() + { + Id = Guid.NewGuid().ToString(), + ProviderId = null, + Uri = $"api://{connectionName}/resource" + } + }); + } + + public Task SignOutUserAsync(string userId, string connectionName, string channelId, CancellationToken cancellationToken) + { + var records = _userTokens.ToArray(); + foreach (var t in records) + { + if (t.Key.ChannelId == channelId && + t.Key.UserId == userId && + (connectionName == null || connectionName == t.Key.ConnectionName)) + { + _userTokens.Remove(t.Key); + } + } + + return Task.CompletedTask; + } + + public Task GetTokenStatusAsync(string userId, string channelId, string includeFilter, CancellationToken cancellationToken) + { + var filter = includeFilter == null ? null : includeFilter.Split(','); + var records = _userTokens. + Where(x => + x.Key.ChannelId == channelId && + x.Key.UserId == userId && + (includeFilter == null || filter.Contains(x.Key.ConnectionName))). + Select(r => new TokenStatus() { ConnectionName = r.Key.ConnectionName, HasToken = true, ServiceProviderDisplayName = r.Key.ConnectionName }).ToArray(); + + if (records.Any()) + { + return Task.FromResult(records); + } + + return Task.FromResult(null); + } + + public Task> GetAadTokensAsync(string userId, string connectionName, string[] resourceUrls, string channelId, CancellationToken cancellationToken) + { + return Task.FromResult(new Dictionary()); + } + + public Task ExchangeTokenAsync(string userId, string connectionName, string channelId, TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken) + { + var exchangableValue = !string.IsNullOrEmpty(exchangeRequest?.Token) ? + exchangeRequest?.Token : + exchangeRequest?.Uri; + + var key = new ExchangableTokenKey() + { + ChannelId = channelId, + ConnectionName = connectionName, + ExchangableItem = exchangableValue, + UserId = userId, + }; + + if (_exchangableToken.TryGetValue(key, out string token)) + { + if (token == ExceptionExpected) + { + throw new InvalidOperationException("Exception occurred during exchanging tokens"); + } + + return Task.FromResult(new TokenResponse() + { + ChannelId = key.ChannelId, + ConnectionName = key.ConnectionName, + Token = token + }); + } + else + { + return Task.FromResult(null); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + } + + class UserTokenKey + { + public string ConnectionName { get; set; } + + public string UserId { get; set; } + + public string ChannelId { get; set; } + + public override bool Equals(object obj) + { + var rhs = obj as UserTokenKey; + if (rhs != null) + { + return string.Equals(this.ConnectionName, rhs.ConnectionName, StringComparison.Ordinal) && + string.Equals(this.UserId, rhs.UserId, StringComparison.Ordinal) && + string.Equals(this.ChannelId, rhs.ChannelId, StringComparison.Ordinal); + } + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return (ConnectionName ?? string.Empty).GetHashCode() + + (UserId ?? string.Empty).GetHashCode() + + (ChannelId ?? string.Empty).GetHashCode(); + } + } + + class ExchangableTokenKey : UserTokenKey + { + public string ExchangableItem { get; set; } + + public override bool Equals(object obj) + { + var rhs = obj as ExchangableTokenKey; + if (rhs != null) + { + return string.Equals(this.ExchangableItem, rhs.ExchangableItem, StringComparison.Ordinal) && + base.Equals(obj); + } + + return false; + } + + public override int GetHashCode() + { + return (ExchangableItem ?? string.Empty).GetHashCode() + + base.GetHashCode(); + } + } + + class TokenMagicCode + { + public UserTokenKey Key { get; set; } + + public string MagicCode { get; set; } + + public string UserToken { get; set; } + } + +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs b/src/tests/BotBuilder.Testing/Adapters/SimpleAdapter.cs similarity index 98% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs rename to src/tests/BotBuilder.Testing/Adapters/SimpleAdapter.cs index 9d051d28..b386ea84 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SimpleAdapter.cs +++ b/src/tests/BotBuilder.Testing/Adapters/SimpleAdapter.cs @@ -9,7 +9,7 @@ using Microsoft.Agents.Core.Models; using Xunit; -namespace Microsoft.Agents.BotBuilder.Tests +namespace Microsoft.Agents.BotBuilder.Testing { public class SimpleAdapter : ChannelAdapter { diff --git a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs index 533b1ad7..4dc7b247 100644 --- a/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs +++ b/src/tests/BotBuilder.Testing/Adapters/TestAdapter.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Testing.Adapters; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Logging; @@ -18,16 +19,12 @@ namespace Microsoft.Agents.BotBuilder.Testing /// A mock adapter that can be used for unit testing of bot logic. /// /// - public class TestAdapter : ChannelAdapter, IUserTokenClient + public class TestAdapter : ChannelAdapter { - private const string ExceptionExpected = "ExceptionExpected"; - private bool _sendTraceActivity; private readonly object _conversationLock = new object(); private readonly object _activeQueueLock = new object(); - private readonly IDictionary _userTokens = new Dictionary(); - private readonly IDictionary _exchangableToken = new Dictionary(); - private readonly IList _magicCodes = new List(); + private readonly MockUserTokenClient _userTokenClient = new MockUserTokenClient(); private int _nextId = 0; private Queue> _queuedRequests = new Queue>(); @@ -473,33 +470,7 @@ public virtual Task SendTextToBotAsync(string userSays, BotCallbackHandler callb /// The optional magic code to associate with this token. public void AddUserToken(string connectionName, string channelId, string userId, string token, string magicCode = null) { - var key = new UserTokenKey() - { - ConnectionName = connectionName, - ChannelId = channelId, - UserId = userId, - }; - - if (magicCode == null) - { - if (_userTokens.ContainsKey(key)) - { - _userTokens[key] = token; - } - else - { - _userTokens.Add(key, token); - } - } - else - { - _magicCodes.Add(new TokenMagicCode() - { - Key = key, - MagicCode = magicCode, - UserToken = token, - }); - } + _userTokenClient.AddUserToken(connectionName, channelId, userId, token, magicCode); } /// @@ -512,22 +483,7 @@ public void AddUserToken(string connectionName, string channelId, string userId, /// The token to store. public void AddExchangeableToken(string connectionName, string channelId, string userId, string exchangableItem, string token) { - var key = new ExchangableTokenKey() - { - ConnectionName = connectionName, - ChannelId = channelId, - UserId = userId, - ExchangableItem = exchangableItem - }; - - if (_exchangableToken.ContainsKey(key)) - { - _exchangableToken[key] = token; - } - else - { - _exchangableToken.Add(key, token); - } + _userTokenClient.AddExchangeableToken(connectionName, channelId,userId, exchangableItem, token); } /// Adds an instruction to throw an exception during exchange requests. @@ -538,151 +494,9 @@ public void AddExchangeableToken(string connectionName, string channelId, string /// The exchangeable token or resource URI. public void ThrowOnExchangeRequest(string connectionName, string channelId, string userId, string exchangableItem) { - var key = new ExchangableTokenKey() - { - ConnectionName = connectionName, - ChannelId = channelId, - UserId = userId, - ExchangableItem = exchangableItem - }; - - if (_exchangableToken.ContainsKey(key)) - { - _exchangableToken[key] = ExceptionExpected; - } - else - { - _exchangableToken.Add(key, ExceptionExpected); - } - } - - public Task GetUserTokenAsync(string userId, string connectionName, string channelId, string magicCode, CancellationToken cancellationToken) - { - var key = new UserTokenKey() - { - ConnectionName = connectionName, - ChannelId = channelId, //turnContext.Activity.ChannelId, - UserId = userId //turnContext.Activity.From.Id, - }; - - if (magicCode != null) - { - var magicCodeRecord = _magicCodes.FirstOrDefault(x => key.Equals(x.Key)); - if (magicCodeRecord != null && magicCodeRecord.MagicCode == magicCode) - { - // move the token to long term dictionary - AddUserToken(connectionName, key.ChannelId, key.UserId, magicCodeRecord.UserToken); - _magicCodes.Remove(magicCodeRecord); - } - } - - if (_userTokens.TryGetValue(key, out string token)) - { - // found - return Task.FromResult(new TokenResponse() - { - ConnectionName = connectionName, - Token = token, - }); - } - - // not found - return Task.FromResult(null); - - } - - public Task GetSignInResourceAsync(string connectionName, IActivity activity, string finalRedirect, CancellationToken cancellationToken) - { - return Task.FromResult(new SignInResource() - { - SignInLink = $"https://fake.com/oauthsignin/{connectionName}/{activity.ChannelId}/{activity?.Recipient?.Id}", - TokenExchangeResource = new TokenExchangeResource() - { - Id = Guid.NewGuid().ToString(), - ProviderId = null, - Uri = $"api://{connectionName}/resource" - } - }); - } - - public Task SignOutUserAsync(string userId, string connectionName, string channelId, CancellationToken cancellationToken) - { - var records = _userTokens.ToArray(); - foreach (var t in records) - { - if (t.Key.ChannelId == channelId && - t.Key.UserId == userId && - (connectionName == null || connectionName == t.Key.ConnectionName)) - { - _userTokens.Remove(t.Key); - } - } - - return Task.CompletedTask; - } - - public Task GetTokenStatusAsync(string userId, string channelId, string includeFilter, CancellationToken cancellationToken) - { - var filter = includeFilter == null ? null : includeFilter.Split(','); - var records = _userTokens. - Where(x => - x.Key.ChannelId == channelId && - x.Key.UserId == userId && - (includeFilter == null || filter.Contains(x.Key.ConnectionName))). - Select(r => new TokenStatus() { ConnectionName = r.Key.ConnectionName, HasToken = true, ServiceProviderDisplayName = r.Key.ConnectionName }).ToArray(); - - if (records.Any()) - { - return Task.FromResult(records); - } - - return Task.FromResult(null); - } - - public Task> GetAadTokensAsync(string userId, string connectionName, string[] resourceUrls, string channelId, CancellationToken cancellationToken) - { - return Task.FromResult(new Dictionary()); + _userTokenClient.ThrowOnExchangeRequest(connectionName,channelId, userId, exchangableItem); } - public Task ExchangeTokenAsync(string userId, string connectionName, string channelId, TokenExchangeRequest exchangeRequest, CancellationToken cancellationToken) - { - var exchangableValue = !string.IsNullOrEmpty(exchangeRequest?.Token) ? - exchangeRequest?.Token : - exchangeRequest?.Uri; - - var key = new ExchangableTokenKey() - { - ChannelId = channelId, - ConnectionName = connectionName, - ExchangableItem = exchangableValue, - UserId = userId, - }; - - if (_exchangableToken.TryGetValue(key, out string token)) - { - if (token == ExceptionExpected) - { - throw new InvalidOperationException("Exception occurred during exchanging tokens"); - } - - return Task.FromResult(new TokenResponse() - { - ChannelId = key.ChannelId, - ConnectionName = key.ConnectionName, - Token = token - }); - } - else - { - return Task.FromResult(null); - } - } - - public void Dispose() - { - GC.SuppressFinalize(this); - } - /// /// Creates the turn context for the adapter. /// @@ -692,7 +506,7 @@ public virtual TurnContext CreateTurnContext(IActivity activity) { var turnContext = new TurnContext(this, activity); - turnContext.Services.Set(this); + turnContext.Services.Set(_userTokenClient); return turnContext; } @@ -716,66 +530,5 @@ private void Enqueue(IActivity activity) ActiveQueue.Enqueue(activity); } } - - private class UserTokenKey - { - public string ConnectionName { get; set; } - - public string UserId { get; set; } - - public string ChannelId { get; set; } - - public override bool Equals(object obj) - { - var rhs = obj as UserTokenKey; - if (rhs != null) - { - return string.Equals(this.ConnectionName, rhs.ConnectionName, StringComparison.Ordinal) && - string.Equals(this.UserId, rhs.UserId, StringComparison.Ordinal) && - string.Equals(this.ChannelId, rhs.ChannelId, StringComparison.Ordinal); - } - - return base.Equals(obj); - } - - public override int GetHashCode() - { - return (ConnectionName ?? string.Empty).GetHashCode() + - (UserId ?? string.Empty).GetHashCode() + - (ChannelId ?? string.Empty).GetHashCode(); - } - } - - private class ExchangableTokenKey : UserTokenKey - { - public string ExchangableItem { get; set; } - - public override bool Equals(object obj) - { - var rhs = obj as ExchangableTokenKey; - if (rhs != null) - { - return string.Equals(this.ExchangableItem, rhs.ExchangableItem, StringComparison.Ordinal) && - base.Equals(obj); - } - - return false; - } - - public override int GetHashCode() - { - return (ExchangableItem ?? string.Empty).GetHashCode() + - base.GetHashCode(); - } - } - - private class TokenMagicCode - { - public UserTokenKey Key { get; set; } - - public string MagicCode { get; set; } - - public string UserToken { get; set; } - } } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs new file mode 100644 index 00000000..61e062e5 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System.Threading.Tasks; +using System.Threading; +using Xunit; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; + +namespace Microsoft.Agents.BotBuilder.Tests.App +{ + public class CommandActivity + { + [Fact] + public async Task CommandBotTest() + { + var adapter = new TestAdapter(); + + // Create mock Activity for testing. + var commandActivity = new Activity + { + Type = ActivityTypes.Command, + Name = "channel/vnd.microsoft.test.multiply", + Value = new MathCommand { First = 2, Second = 2 } + }; + + var unknownCommandActivity = new Activity + { + Type = ActivityTypes.Command, + Name = "channel/vnd.microsoft.test.divide", + Value = new MathCommand { First = 10, Second = 2 } + }; + + await new TestFlow(adapter, new CommandBot(new ApplicationOptions())) + .Send(commandActivity) + .AssertReply((activity) => + { + Assert.Equal(commandActivity.Name, activity.Name); + + var result = ProtocolJsonSerializer.ToObject>(activity.Value); + Assert.Equal(4, result.Data.Result); + }) + .Send(unknownCommandActivity) + .AssertReply((activity) => + { + Assert.Equal(unknownCommandActivity.Name, activity.Name); + + var result = ProtocolJsonSerializer.ToObject>(activity.Value); + Assert.Equal("NotSupported", result.Error.Code); + }) + .StartTestAsync(); + } + } + + class CommandBot : Application + { + public CommandBot(ApplicationOptions options) : base(options) + { + OnActivity(ActivityTypes.Command, OnCommandAsync); + } + + public static async Task OnCommandAsync(ITurnContext turnContext, ITurnState state, CancellationToken cancellationToken) + { + if (turnContext.Activity.Name == "channel/vnd.microsoft.test.multiply") + { + var value = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + + var commandResult = new Activity() + { + Type = "commandResult", + Name = turnContext.Activity.Name, + Value = new CommandResultValue + { + Data = new MathResult { Result = value.First * value.Second } + } + }; + + await turnContext.SendActivityAsync(commandResult, cancellationToken); + } + else + { + var commandResult = new Activity() + { + Type = "commandResult", + Name = turnContext.Activity.Name, + Value = new CommandResultValue + { + Error = new Error + { + Code = "NotSupported" + } + } + }; + + await turnContext.SendActivityAsync(commandResult, cancellationToken); + } + } + } + + class MathCommand + { + public int First; + public int Second; + } + + class MathResult + { + public int Result; + } +} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs index f827788f..9abc54dc 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Moq; @@ -14,7 +13,7 @@ using System.Threading.Tasks; using Xunit; -namespace Microsoft.Agents.BotBuilder.Tests +namespace Microsoft.Agents.BotBuilder.Tests.Handler { public class ActivityHandlerTests { @@ -658,7 +657,7 @@ public async Task TestDelegatingTurnContext() turnContextMock.Setup(tc => tc.StackState).Returns(new TurnContextStateCollection()); turnContextMock.Setup(tc => tc.Services).Returns(new TurnContextStateCollection()); - turnContextMock.Object.Services.Set(new Mock().Object); + turnContextMock.Object.Services.Set(new Mock().Object); turnContextMock.Setup(tc => tc.Responded).Returns(false); turnContextMock.Setup(tc => tc.OnDeleteActivity(It.IsAny())); turnContextMock.Setup(tc => tc.OnSendActivities(It.IsAny())); @@ -770,7 +769,7 @@ public async Task OnTurnAsync_ShouldThrowOnNullContext() var bot = new TestActivityHandler(); //Assert - await Assert.ThrowsAsync(async () => await ((IBot)bot).OnTurnAsync(turnContext));; + await Assert.ThrowsAsync(async () => await ((IBot)bot).OnTurnAsync(turnContext)); ; } [Fact] diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/Command.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/Command.cs new file mode 100644 index 00000000..a1f075ca --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/Command.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System.Threading.Tasks; +using System.Threading; +using Xunit; +using Microsoft.Agents.BotBuilder.Compat; + +namespace Microsoft.Agents.BotBuilder.Tests.Handler +{ + public class CommandActivity + { + [Fact] + public async Task CommandBotTest() + { + var adapter = new TestAdapter(); + + // Create mock Activity for testing. + var commandActivity = new Activity + { + Type = ActivityTypes.Command, + Name = "channel/vnd.microsoft.test.multiply", + Value = new MathCommand { First = 2, Second = 2 } + }; + + var unknownCommandActivity = new Activity + { + Type = ActivityTypes.Command, + Name = "channel/vnd.microsoft.test.divide", + Value = new MathCommand { First = 10, Second = 2 } + }; + + await new TestFlow(adapter, new CommandBot()) + .Send(commandActivity) + .AssertReply((activity) => + { + Assert.Equal(commandActivity.Name, activity.Name); + + var result = ProtocolJsonSerializer.ToObject>(activity.Value); + Assert.Equal(4, result.Data.Result); + }) + .Send(unknownCommandActivity) + .AssertReply((activity) => + { + Assert.Equal(unknownCommandActivity.Name, activity.Name); + + var result = ProtocolJsonSerializer.ToObject>(activity.Value); + Assert.Equal("NotSupported", result.Error.Code); + }) + .StartTestAsync(); + } + } + + class CommandBot : ActivityHandler + { + protected async override Task OnCommandActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (turnContext.Activity.Name == "channel/vnd.microsoft.test.multiply") + { + var value = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + + var commandResult = new Activity() + { + Type = "commandResult", + Name = turnContext.Activity.Name, + Value = new CommandResultValue + { + Data = new MathResult { Result = value.First * value.Second } + } + }; + + await turnContext.SendActivityAsync(commandResult, cancellationToken); + } + else + { + var commandResult = new Activity() + { + Type = "commandResult", + Name = turnContext.Activity.Name, + Value = new CommandResultValue + { + Error = new Error + { + Code = "NotSupported" + } + } + }; + + await turnContext.SendActivityAsync(commandResult, cancellationToken); + } + } + } + + class MathCommand + { + public int First; + public int Second; + } + + class MathResult + { + public int Result; + } +} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj index cff3755e..669a123f 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Microsoft.Agents.BotBuilder.Tests.csproj @@ -26,7 +26,6 @@ - diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs index 28be2312..37cf2e8c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Moq; diff --git a/src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/Microsoft.Agents.Extensions.SharePoint.Tests.csproj b/src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/Microsoft.Agents.Extensions.SharePoint.Tests.csproj new file mode 100644 index 00000000..00e23f4e --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/Microsoft.Agents.Extensions.SharePoint.Tests.csproj @@ -0,0 +1,29 @@ + + + latest + CplTests.SharePoint + true + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs b/src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/SharePointActivityHandlerTests.cs similarity index 98% rename from src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/SharePointActivityHandlerTests.cs index 115e8155..0dbccee6 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/SharePoint/SharePointActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.SharePoint.Tests/SharePointActivityHandlerTests.cs @@ -11,8 +11,10 @@ using Microsoft.Agents.Extensions.SharePoint.Models; using Microsoft.Agents.Extensions.SharePoint; using Microsoft.Agents.Extensions.SharePoint.Compat; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Testing; -namespace Microsoft.Agents.BotBuilder.Tests.SharePoint +namespace Microsoft.Agents.Extensions.SharePoint.Tests { public class SharePointActivityHandlerTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj index ffb36205..737ac688 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj @@ -24,5 +24,6 @@ + diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs index 7021d92f..0f4c40e0 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/AppBasedLinkQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class AppBasedLinkQueryTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs index 8f70abd0..448ccb4a 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/BotConfigAuthTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class BotConfigAuthTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/CacheInfoTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/CacheInfoTests.cs index 78a76669..661846e6 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/CacheInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/CacheInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class CacheInfoTests { @@ -20,7 +20,7 @@ public void CacheInfoInits() Assert.Equal(cacheType, cacheInfo.CacheType); Assert.Equal(cacheDuration, cacheInfo.CacheDuration); } - + [Fact] public void CacheInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ChannelInfoTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ChannelInfoTests.cs index 6d4a93ed..7265db4c 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ChannelInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ChannelInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ChannelInfoTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigAuthResponseTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigAuthResponseTests.cs index a14512e2..7eb89a80 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigAuthResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigAuthResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ConfigAuthResponseTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigResponseTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigResponseTests.cs index b9444d9d..da83de0c 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ConfigResponseTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigTaskResponseTests.cs similarity index 90% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigTaskResponseTests.cs index eeaab5c5..21a3b50a 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConfigTaskResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConfigTaskResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ConfigTaskResponseTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConversationListTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConversationListTests.cs index 073029fb..7fd05da9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ConversationListTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ConversationListTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ConversationListTests { @@ -26,7 +26,7 @@ public void ConversationListInits() Assert.Equal(conversations[0].Id, conversationList.Conversations[0].Id); Assert.Equal(conversations[1].Id, conversationList.Conversations[1].Id); } - + [Fact] public void ConversationListInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardResponseTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardResponseTests.cs index c116a0f5..2f2f0d1b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class FileConsentCardResponseTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs index f11182bb..45230d43 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileConsentCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class FileConsentCardTests { @@ -25,7 +25,7 @@ public void FileConsentCardInits() Assert.Equal(acceptContext, fileConsentCard.AcceptContext); Assert.Equal(declineContext, fileConsentCard.DeclineContext); } - + [Fact] public void FileConsentCardInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileDownloadInfoTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileDownloadInfoTests.cs index 0ffcb3ed..8faeb9b3 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileDownloadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileDownloadInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class FileDownloadInfoTests { @@ -25,7 +25,7 @@ public void FileDownloadInfoInits() Assert.Equal(fileType, fileDownloadInfo.FileType); Assert.Equal(etag, fileDownloadInfo.Etag); } - + [Fact] public void FileDownloadInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileInfoCardTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileInfoCardTests.cs index 4622aaf2..4f5f6e83 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileInfoCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileInfoCardTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class FileInfoCardTests { @@ -23,7 +23,7 @@ public void FileInfoCardInits() Assert.Equal(fileType, fileInfoCard.FileType); Assert.Equal(etag, fileInfoCard.Etag); } - + [Fact] public void FileInfoCardInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileUploadInfoTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileUploadInfoTests.cs index 5c5b980f..f4377d97 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/FileUploadInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileUploadInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class FileUploadInfoTests { @@ -27,7 +27,7 @@ public void FileUploadInfoInits() Assert.Equal(uniqueId, fileUploadInfo.UniqueId); Assert.Equal(fileType, fileUploadInfo.FileType); } - + [Fact] public void FileUploadInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantInfoTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantInfoTests.cs index 67348874..cdd6a84d 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MeetingParticipantInfoTests { @@ -21,7 +21,7 @@ public void MeetingParticipantInfoInits() Assert.Equal(role, meetingParticipantInfo.Role); Assert.Equal(inMeeting, meetingParticipantInfo.InMeeting); } - + [Fact] public void MeetingParticipantInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantsEventDetailsTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantsEventDetailsTests.cs index efa8fa9d..d244e8ac 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MeetingParticipantsEventDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MeetingParticipantsEventDetailsTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MeetingParticipantsEventDetailsTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAppTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAppTests.cs index 6f18c07f..d7825e8d 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAppTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAppTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadAppTests { @@ -23,7 +23,7 @@ public void MessageActionPayloadAppInits() Assert.Equal(id, messageActionPayloadApp.Id); Assert.Equal(displayName, messageActionPayloadApp.DisplayName); } - + [Fact] public void MessageActionPayloadAppInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAttachmentTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAttachmentTests.cs index 9da3a56f..faba40f7 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadAttachmentTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadAttachmentTests { @@ -29,7 +29,7 @@ public void MessageActionPayloadAttachmentInits() Assert.Equal(name, msgPayloadAttachment.Name); Assert.Equal(thumbnailUrl, msgPayloadAttachment.ThumbnailUrl); } - + [Fact] public void MessageActionPayloadAttachmentInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadBodyTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadBodyTests.cs index 075da79e..3328ccb8 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadBodyTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadBodyTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadBodyTests { @@ -21,7 +21,7 @@ public void MessageActionsPayloadBodyInits() Assert.Equal(contentType, msgActionsPayloadBody.ContentType); Assert.Equal(content, msgActionsPayloadBody.Content); } - + [Fact] public void MessageActionsPayloadBodyInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadConversationsTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadConversationsTests.cs index 572aceb9..ac6f7997 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadConversationsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadConversationsTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadConversationsTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadFromTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadFromTests.cs index 4f84287f..83fe4e60 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadFromTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadFromTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadFromTests { @@ -23,7 +23,7 @@ public void MessageActionsPayloadFromInits() Assert.Equal(application, msgActionsPayloadFrom.Application); Assert.Equal(conversation, msgActionsPayloadFrom.Conversation); } - + [Fact] public void MessageActionsPayloadFromInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadMentionTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadMentionTests.cs index 37fe79c9..648592a0 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadMentionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadMentionTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadMentionTests { @@ -23,7 +23,7 @@ public void MessageActionsPayloadMentionInits() Assert.Equal(mentionText, msgActionsPayloadMention.MentionText); Assert.Equal(mentioned, msgActionsPayloadMention.Mentioned); } - + [Fact] public void MessageActionsPayloadMentionInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadReactionTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadReactionTests.cs index a182c8a9..017b8bdf 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadReactionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadReactionTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadReactionTests { @@ -23,7 +23,7 @@ public void MessageActionsPayloadReactionInits() Assert.Equal(createdDateTime, msgActionsPayloadReaction.CreatedDateTime); Assert.Equal(user, msgActionsPayloadReaction.User); } - + [Fact] public void MessageActionsPayloadReactionInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadTests.cs index 27189b8e..d8b44c7f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadTests.cs @@ -8,7 +8,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { /// /// Tests to ensure that MessageActionsPayload works as expected. diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadUserTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadUserTests.cs index 60ebae28..d014ad51 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessageActionsPayloadUserTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessageActionsPayloadUserTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessageActionsPayloadUserTests { @@ -23,7 +23,7 @@ public void MessageActionsPayloadUserInits() Assert.Equal(id, msgActionsPayloadUser.Id); Assert.Equal(displayName, msgActionsPayloadUser.DisplayName); } - + [Fact] public void MessageActionsPayloadUserInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs index 8d00761a..e5591650 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionActionResponseTests { @@ -17,7 +17,7 @@ public void MessagingExtensionActionResponseInits() var cacheInfo = new CacheInfo(); var msgExtActionResponse = new MessagingExtensionActionResponse(task, composeExtension) - { + { CacheInfo = cacheInfo }; @@ -27,7 +27,7 @@ public void MessagingExtensionActionResponseInits() Assert.Equal(composeExtension, msgExtActionResponse.ComposeExtension); Assert.Equal(cacheInfo, msgExtActionResponse.CacheInfo); } - + [Fact] public void MessagingExtensionActionResponseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs index fb04a088..fe3e2a04 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionActionTests { @@ -23,7 +23,7 @@ public void MessagingExtensionActionInits() var state = "secureOAuthState1234"; var msgExtAction = new MessagingExtensionAction(data, context, commandId, commandContext, botMessagePreviewAction, botActivityPreview, messagePayload) - { + { State = state }; @@ -38,7 +38,7 @@ public void MessagingExtensionActionInits() Assert.Equal(messagePayload, msgExtAction.MessagePayload); Assert.Equal(state, msgExtAction.State); } - + [Fact] public void MessagingExtensionActionInitsNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionAttachmentTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionAttachmentTests.cs index 26d4aa33..4c319dae 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionAttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionAttachmentTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionAttachmentTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionParametersTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionParametersTests.cs index e441d252..30c6a378 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionParametersTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionParametersTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionParametersTests { @@ -21,7 +21,7 @@ public void MessagingExtensionParametersInits() Assert.Equal(name, msgExtParams.Name); Assert.Equal(value, msgExtParams.Value); } - + [Fact] public void MessagingExtensionParametersInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryOptionsTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryOptionsTests.cs index 36656190..c59ddad1 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryOptionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryOptionsTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionQueryOptionsTests { @@ -21,7 +21,7 @@ public void MessagingExtensionQueryOptionsInits() Assert.Equal(skip, msgExtQueryOptions.Skip); Assert.Equal(count, msgExtQueryOptions.Count); } - + [Fact] public void MessagingExtensionQueryOptionsInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs index b09ab334..2990d3be 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionQueryTests { @@ -26,7 +26,7 @@ public void MessagingExtensionQueryInits() Assert.Equal(queryOptions, msgExtQuery.QueryOptions); Assert.Equal(state, msgExtQuery.State); } - + [Fact] public void MessagingExtensionQueryInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResponseTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResponseTests.cs index d328d92d..9e5a0e98 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionResponseTests { @@ -15,7 +15,7 @@ public void MessagingExtensionResponseInits() var cacheInfo = new CacheInfo(); var msgExtResponse = new MessagingExtensionResponse(composeExtension) - { + { CacheInfo = cacheInfo }; @@ -24,7 +24,7 @@ public void MessagingExtensionResponseInits() Assert.Equal(composeExtension, msgExtResponse.ComposeExtension); Assert.Equal(cacheInfo, msgExtResponse.CacheInfo); } - + [Fact] public void MessagingExtensionResponseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResultTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResultTests.cs index d7e0dbfa..a23bcd92 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionResultTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionResultTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionResultTests { @@ -18,7 +18,7 @@ public void MessagingExtensionResultInits() var attachments = new List() { new MessagingExtensionAttachment() }; var suggestedActions = new MessagingExtensionSuggestedAction( new List() - { + { new CardAction("showImage"), new CardAction("openUrl"), }); @@ -36,7 +36,7 @@ public void MessagingExtensionResultInits() Assert.Equal(text, msgExtResult.Text); Assert.Equal(activityPreview, msgExtResult.ActivityPreview); } - + [Fact] public void MessagingExtensionResultInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionSuggestedActionTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionSuggestedActionTests.cs index 8220ebfd..2284c044 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/MessagingExtensionSuggestedActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionSuggestedActionTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class MessagingExtensionSuggestedActionTests { @@ -19,7 +19,7 @@ public void MessagingExtensionSuggestedActionInits() new CardAction("imBack"), new CardAction("postBack"), }; - + var msgExtSuggestedAction = new MessagingExtensionSuggestedAction(cardActions); Assert.NotNull(msgExtSuggestedAction); @@ -27,7 +27,7 @@ public void MessagingExtensionSuggestedActionInits() Assert.Equal(cardActions, msgExtSuggestedAction.Actions); Assert.Equal(cardActions.Count, msgExtSuggestedAction.Actions.Count); } - + [Fact] public void MessagingExtensionSuggestedActionInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotImplementedAdapter.cs similarity index 88% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotImplementedAdapter.cs index dea53368..7f184d74 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotImplementedAdapter.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotImplementedAdapter.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { internal class NotImplementedAdapter : ChannelAdapter { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotificationInfoTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotificationInfoTests.cs index 7c74a1ad..94349b81 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/NotificationInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/NotificationInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class NotificationInfoTests { @@ -26,7 +26,7 @@ public void NotificationInfoInits() Assert.True(notificationInfo.AlertInMeeting); Assert.Equal(externalResourceUrl, notificationInfo.ExternalResourceUrl); } - + [Fact] public void NotificationInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionBaseTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionBaseTests.cs index b2a03f43..e51f232f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionBaseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardActionBaseTests { @@ -23,7 +23,7 @@ public void O365ConnectorCardActionBaseInits() Assert.Equal(name, o365ConnectorCardActionBase.Name); Assert.Equal(id, o365ConnectorCardActionBase.Id); } - + [Fact] public void O365ConnectorCardActionBaseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionCardTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionCardTests.cs index 22692aa8..98c3af51 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionCardTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardActionCardTests { @@ -26,7 +26,7 @@ public void O365ConnectorCardActionCardInits() Assert.Equal(inputs, o365ConnectorCardActionCard.Inputs); Assert.Equal(actions, o365ConnectorCardActionCard.Actions); } - + [Fact] public void O365ConnectorCardActionCardInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionQueryTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionQueryTests.cs index 5c6be84d..69913ac0 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardActionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardActionQueryTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardActionQueryTests { @@ -21,7 +21,7 @@ public void O365ConnectorCardActionQueryInits() Assert.Equal(body, actionQuery.Body); Assert.Equal(actionId, actionQuery.ActionId); } - + [Fact] public void O365ConnectorCardActionQueryInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardDateInputTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardDateInputTests.cs index 0a263da2..7e9a7eea 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardDateInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardDateInputTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardDateInputTests { @@ -28,7 +28,7 @@ public void O365ConnectorCardDateInputInits() Assert.Equal(value, dateInput.Value); Assert.Equal(includeTime, dateInput.IncludeTime); } - + [Fact] public void O365ConnectorCardDateInputInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardFactTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardFactTests.cs index b0feec7c..ef3de0fb 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardFactTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardFactTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardFactTests { @@ -21,7 +21,7 @@ public void O365ConnectorCardFactInits() Assert.Equal(name, fact.Name); Assert.Equal(value, fact.Value); } - + [Fact] public void O365ConnectorCardFactInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardHttpPOSTTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardHttpPOSTTests.cs index 5dc3755f..7e079c11 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardHttpPOSTTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardHttpPOSTTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardHttpPOSTTests { @@ -25,7 +25,7 @@ public void O365ConnectorCardHttpPOSTInits() Assert.Equal(id, httpPOST.Id); Assert.Equal(body, httpPOST.Body); } - + [Fact] public void O365ConnectorCardHttpPOSTInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardImageTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardImageTests.cs index 301e36c4..be89551a 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardImageTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardImageTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardImageTests { @@ -21,7 +21,7 @@ public void O365ConnectorCardImageInits() Assert.Equal(image, cardImage.Image); Assert.Equal(title, cardImage.Title); } - + [Fact] public void O365ConnectorCardImageInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardInputBaseTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardInputBaseTests.cs index 7f467b79..37f5ea95 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardInputBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardInputBaseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardInputBaseTests { @@ -27,7 +27,7 @@ public void O365ConnectorCardInputBaseInits() Assert.Equal(title, inputBase.Title); Assert.Equal(value, inputBase.Value); } - + [Fact] public void O365ConnectorCardInputBaseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputChoiceTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputChoiceTests.cs index 4d499b00..aed0601f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputChoiceTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputChoiceTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardMultichoiceInputChoiceTests { @@ -21,7 +21,7 @@ public void O365ConnectorCardMultichoiceInputChoiceInits() Assert.Equal(display, choice.Display); Assert.Equal(value, choice.Value); } - + [Fact] public void O365ConnectorCardMultichoiceInputChoiceInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputTests.cs index 4711d2c9..66d6d95f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardMultichoiceInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardMultichoiceInputTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardMultichoiceInputTests { @@ -18,9 +18,9 @@ public void O365ConnectorCardMultichoiceInputInits() var title = "Books"; var value = "No Book Selected"; var choices = new List() - { + { new O365ConnectorCardMultichoiceInputChoice("C# In Depth"), - new O365ConnectorCardMultichoiceInputChoice("C# in a Nutshell"), + new O365ConnectorCardMultichoiceInputChoice("C# in a Nutshell"), }; var style = "expanded"; var isMultiSelect = true; @@ -38,7 +38,7 @@ public void O365ConnectorCardMultichoiceInputInits() Assert.Equal(style, multichoiceInput.Style); Assert.Equal(isMultiSelect, multichoiceInput.IsMultiSelect); } - + [Fact] public void O365ConnectorCardMultichoiceInputInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTargetTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTargetTests.cs index d7129f64..85915f21 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTargetTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTargetTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardOpenUriTargetTests { @@ -21,7 +21,7 @@ public void O365ConnectorCardOpenUriTargetInits() Assert.Equal(os, openUriTarget.Os); Assert.Equal(uri, openUriTarget.Uri); } - + [Fact] public void O365ConnectorCardOpenUriTargetInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTests.cs index fa89f004..bf4106ae 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardOpenUriTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardOpenUriTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardOpenUriTests { @@ -26,7 +26,7 @@ public void O365ConnectorCardOpenUriInits() Assert.Equal(targets, openUri.Targets); Assert.Single(openUri.Targets); } - + [Fact] public void O365ConnectorCardOpenUriInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardSectionTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardSectionTests.cs index afa31d28..23ec3c2d 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardSectionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardSectionTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardSectionTests { @@ -26,9 +26,9 @@ public void O365ConnectorCardSectionInits() new O365ConnectorCardFact("powdered"), }; var images = new List() - { + { new O365ConnectorCardImage("https://jelly.com"), - new O365ConnectorCardImage("https://powdered.com") + new O365ConnectorCardImage("https://powdered.com") }; var potentialAction = new List() { new O365ConnectorCardActionBase("OpenUri") }; @@ -51,7 +51,7 @@ public void O365ConnectorCardSectionInits() Assert.Equal(potentialAction, section.PotentialAction); Assert.Single(section.PotentialAction); } - + [Fact] public void O365ConnectorCardSectionInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTests.cs index caa87d1b..17739889 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardTests { @@ -30,7 +30,7 @@ public void O365ConnectorCardInits() Assert.Equal(sections, o365ConnectorCard.Sections); Assert.Equal(potentialAction, o365ConnectorCard.PotentialAction); } - + [Fact] public void O365ConnectorCardInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTextInputTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTextInputTests.cs index b7631e34..91a74f34 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardTextInputTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardTextInputTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardTextInputTests { @@ -30,7 +30,7 @@ public void O365ConnectorCardTextInputInits() Assert.Equal(isMultiline, textInput.IsMultiline); Assert.Equal(maxLength, textInput.MaxLength); } - + [Fact] public void O365ConnectorCardTextInputInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardViewActionTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardViewActionTests.cs index 0e37eb97..20f0d5f9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/O365ConnectorCardViewActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/O365ConnectorCardViewActionTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class O365ConnectorCardViewActionTests { @@ -26,7 +26,7 @@ public void O365ConnectorCardViewActionInits() Assert.Equal(target, viewAction.Target); Assert.Single(viewAction.Target); } - + [Fact] public void O365ConnectorCardViewActionInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ReadReceiptInfoTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ReadReceiptInfoTests.cs index f429ae94..00fcb14b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/ReadReceiptInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/ReadReceiptInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class ReadReceiptInfoTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SigninStateVerificationQueryTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SigninStateVerificationQueryTests.cs index e7fb4f42..d98737c5 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SigninStateVerificationQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SigninStateVerificationQueryTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class SigninStateVerificationQueryTests { @@ -12,14 +12,14 @@ public class SigninStateVerificationQueryTests public void SigninStateVerificationQueryInits() { var state = "OK"; - + var verificationQuery = new SigninStateVerificationQuery(state); Assert.NotNull(verificationQuery); Assert.IsType(verificationQuery); Assert.Equal(state, verificationQuery.State); } - + [Fact] public void SigninStateVerificationQueryInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SurfaceTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SurfaceTests.cs index 9baa0721..8b9062de 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SurfaceTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/SurfaceTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class SurfaceTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabContextTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabContextTests.cs index 21d3d547..ea5f94f9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabContextTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabContextTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabEntityContextTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabEntityContextTests.cs index c2e7ad26..0197cec6 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabEntityContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabEntityContextTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabEntityContextTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabRequestTests.cs similarity index 87% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabRequestTests.cs index 800a82e7..4bd5256a 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabRequestTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabRequestTests.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabRequestTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardTests.cs index 8945128d..a8194a17 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabResponseCardTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardsTests.cs similarity index 84% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardsTests.cs index 055bdb99..2f125e29 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseCardsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseCardsTests.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabResponseCardsTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponsePayloadTests.cs similarity index 85% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponsePayloadTests.cs index 267d7fb6..5172e1cc 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponsePayloadTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponsePayloadTests.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabResponsePayloadTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseTests.cs similarity index 82% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseTests.cs index 0af4f11d..5cd0493e 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabResponseTests.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabResponseTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitDataTests.cs similarity index 88% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitDataTests.cs index effc597d..eb899633 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitDataTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitDataTests.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; using System.Text.Json; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabSubmitDataTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitTests.cs similarity index 87% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitTests.cs index f08fc9d2..04fd0d25 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSubmitTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSubmitTests.cs @@ -3,9 +3,9 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabSubmitTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSuggestedActionsTests.cs similarity index 85% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSuggestedActionsTests.cs index 29ee7401..05ebb99a 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabSuggestedActionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabSuggestedActionsTests.cs @@ -5,9 +5,9 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -using static Microsoft.Agents.Extensions.Teams.Tests.TabsTestData; +using static Microsoft.Agents.Extensions.Teams.Tests.Model.TabsTestData; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TabSuggestedActionsTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabsTestData.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabsTestData.cs index 3125742f..0607327f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TabsTestData.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TabsTestData.cs @@ -8,7 +8,7 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { internal class TabsTestData { @@ -56,7 +56,7 @@ public IEnumerator GetEnumerator() { new TabResponseCard(), new TabResponseCard(), - } + } }; } @@ -117,7 +117,7 @@ public IEnumerator GetEnumerator() yield return new object[] { "pdf", - ProtocolJsonSerializer.ToJsonElements( new { key = "value" }) + (new { key = "value" }).ToJsonElements( ) }; } @@ -130,12 +130,12 @@ public IEnumerator GetEnumerator() { yield return new object[] { null }; yield return new object[] - { + { new List() - { + { new CardAction(), new CardAction(), - } + } }; } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleActionTests.cs similarity index 89% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleActionTests.cs index eaf43758..b0f0e1de 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleActionTests.cs @@ -6,7 +6,7 @@ using System.Text.Json; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleActionTests { @@ -28,7 +28,7 @@ public void TaskModuleActionInits(string title, object value) Assert.NotNull(action); Assert.IsType(action); Assert.Equal(title, action.Title); - var valAsObj = ProtocolJsonSerializer.ToJsonElements(action.Value); + var valAsObj = action.Value.ToJsonElements(); Assert.True(valAsObj.ContainsKey(expectedKey)); Assert.Equal(expectedVal, valAsObj[expectedKey].ToString()); } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleCardResponseTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleCardResponseTests.cs index 5fb9cf8e..745d8cb9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleCardResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleCardResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleCardResponseTests { @@ -18,7 +18,7 @@ public void TaskModuleCardResponseInits() Assert.IsType(cardResponse); Assert.Equal(value, cardResponse.Value); } - + [Fact] public void TaskModuleCardResponseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleContinueResponseTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleContinueResponseTests.cs index ac75723c..c00c745b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleContinueResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleContinueResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleContinueResponseTests { @@ -20,7 +20,7 @@ public void TaskModuleContinueResponseInits() Assert.Equal(value, continueResponse.Value); Assert.Equal("continue", continueResponse.Type); } - + [Fact] public void TaskModuleContinueResponseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleMessageResponseTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleMessageResponseTests.cs index 95c5a28c..c64f7c5b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleMessageResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleMessageResponseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleMessageResponseTests { @@ -20,7 +20,7 @@ public void TaskModuleMessageResponseInits() Assert.Equal(value, messageResponse.Value); Assert.Equal("message", messageResponse.Type); } - + [Fact] public void TaskModuleMessageResponseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestContextTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestContextTests.cs index 6dfd6593..9c8c1ff9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestContextTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestContextTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleRequestContextTests { @@ -19,7 +19,7 @@ public void TaskModuleRequestContextInits() Assert.IsType(requestContext); Assert.Equal(theme, requestContext.Theme); } - + [Fact] public void TaskModuleRequestContextInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestTests.cs index 5107658c..b46a53d2 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleRequestTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleRequestTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleRequestTests { @@ -26,7 +26,7 @@ public void TaskModuleRequestInits() Assert.Equal(context, request.Context); Assert.Equal(tabEntityContext, request.TabEntityContext); } - + [Fact] public void TaskModuleRequestInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseBaseTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseBaseTests.cs index 26f552bb..e260f024 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseBaseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseBaseTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleResponseBaseTests { @@ -19,7 +19,7 @@ public void TaskModuleResponseBaseInits() Assert.IsType(responseBase); Assert.Equal(type, responseBase.Type); } - + [Fact] public void TaskModuleResponseBaseInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseTests.cs similarity index 94% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseTests.cs index 2b5515e9..93c8eced 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleResponseTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleResponseTests { @@ -25,7 +25,7 @@ public void TaskModuleResponseInits() Assert.Equal(task, response.Task); Assert.Equal(cacheInfo, response.CacheInfo); } - + [Fact] public void TaskModuleResponseInitsWithNoArgs() { @@ -69,7 +69,7 @@ public void TaskModuleResponseRoundTrip() // Would need a converter for TaskModuleContinueResponse to get the actual type. Otherwise use // ToObject. - var valueElements = ProtocolJsonSerializer.ToJsonElements(task.Properties["value"]); + var valueElements = task.Properties["value"].ToJsonElements(); Assert.True(valueElements.ContainsKey("height")); Assert.True(valueElements.ContainsKey("width")); Assert.True(valueElements.ContainsKey("title")); diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleTaskInfoTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleTaskInfoTests.cs index 92ca219f..e1f5dbc8 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TaskModuleTaskInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TaskModuleTaskInfoTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TaskModuleTaskInfoTests { @@ -33,7 +33,7 @@ public void TaskModuleTaskInfoInits() Assert.Equal(fallbackUrl, taskInfo.FallbackUrl); Assert.Equal(completionBotId, taskInfo.CompletionBotId); } - + [Fact] public void TaskModuleTaskInfoInitsWithNoArgs() { @@ -74,7 +74,7 @@ public void TaskModuleTaskMessagingExtensionResponse() // Would need a converter for TaskModuleContinueResponse to get the actual type. Otherwise use // ToObject. - var valueElements = ProtocolJsonSerializer.ToJsonElements(task.Properties["value"]); + var valueElements = task.Properties["value"].ToJsonElements(); Assert.True(valueElements.ContainsKey("height")); Assert.True(valueElements.ContainsKey("width")); Assert.True(valueElements.ContainsKey("title")); diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamDetailsTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamDetailsTests.cs index 0408a48e..44618d8e 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamDetailsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamDetailsTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamDetailsTests { @@ -34,7 +34,7 @@ public void TeamDetailsInits() Assert.Equal(memberCount, teamDetails.MemberCount); Assert.Equal(type, teamDetails.Type); } - + [Fact] public void TeamDetailsInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamInfoTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamInfoTests.cs index c8d7a812..df536bb9 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamInfoTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs index 977e39bc..37366f6f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs @@ -10,8 +10,9 @@ using System; using System.Globalization; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Testing; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsActivityHandlerTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs index ac9fea39..d9939373 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsChannelAccountTests { @@ -29,7 +29,7 @@ public void TeamChannelAccountInits() Assert.Equal(email, teamsChannelAccount.Email); Assert.Equal(userPrincipalName, teamsChannelAccount.UserPrincipalName); } - + [Fact] public void TeamChannelAccountInitsWithAllOptions() { @@ -60,7 +60,7 @@ public void TeamChannelAccountInitsWithAllOptions() Assert.Equal(userRole, teamsChannelAccount.UserRole); Assert.Equal(objectId, teamsChannelAccount.AadObjectId); } - + [Fact] public void TeamChannelAccountInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs similarity index 98% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs index a1b2f0ba..94e55acd 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsChannelDataTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs @@ -9,7 +9,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsChannelDataTests { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingInfoTests.cs similarity index 92% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingInfoTests.cs index 93ea8288..6067f4c3 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsMeetingInfoTests { @@ -19,7 +19,7 @@ public void TeamsMeetingInfoInits() Assert.IsType(meetingInfo); Assert.Equal(id, meetingInfo.Id); } - + [Fact] public void TeamsMeetingInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingParticipantTests.cs similarity index 95% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingParticipantTests.cs index ed516fa2..fa7b8832 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsMeetingParticipantTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsMeetingParticipantTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsMeetingParticipantTests { @@ -24,7 +24,7 @@ public void TeamsMeetingParticipantInits() Assert.Equal(conversation, participant.Conversation); Assert.Equal(meeting, participant.Meeting); } - + [Fact] public void TeamsMeetingParticipantInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsPagedMembersResultTests.cs similarity index 96% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsPagedMembersResultTests.cs index 8338111c..eb710f1c 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsPagedMembersResultTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsPagedMembersResultTests.cs @@ -6,7 +6,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsPagedMembersResultTests { @@ -33,7 +33,7 @@ public void TeamsPagedMemberResultInits() Assert.Equal(channel1Id, resultMembers[0].Id); Assert.Equal(channel2Id, resultMembers[1].Id); } - + [Fact] public void TeamsPagedMemberResultInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsParticipantChannelAccountTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsParticipantChannelAccountTests.cs index c71355d1..b79396a6 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TeamsParticipantChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsParticipantChannelAccountTests.cs @@ -5,7 +5,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TeamsParticipantChannelAccountTests { @@ -40,7 +40,7 @@ public void TeamsParticipantChannelAccountInits() Assert.Equal(inMeeting, participantAccount.InMeeting); Assert.Equal(conversation, participantAccount.Conversation); } - + [Fact] public void TeamsParticipantChannelAccountInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TenantInfoTests.cs similarity index 91% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TenantInfoTests.cs index aeb058ac..4e112bbb 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TenantInfoTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TenantInfoTests.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Xunit; -namespace Microsoft.Agents.Extensions.Teams.Tests +namespace Microsoft.Agents.Extensions.Teams.Tests.Model { public class TenantInfoTests { @@ -12,14 +12,14 @@ public class TenantInfoTests public void TenantInfoInits() { var id = "123456-7890-abcd-efgh-ijklmno"; - + var tenantInfo = new TenantInfo(id); Assert.NotNull(tenantInfo); Assert.IsType(tenantInfo); Assert.Equal(id, tenantInfo.Id); } - + [Fact] public void TenantInfoInitsWithNoArgs() { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs deleted file mode 100644 index b33db6bc..00000000 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/SimpleAdapter.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Core.Models; -using Xunit; - -namespace Microsoft.Agents.Extensions.Teams.Tests -{ - public class SimpleAdapter : ChannelAdapter - { - private readonly Action _callOnSend = null; - private readonly Action _callOnUpdate = null; - private readonly Action _callOnDelete = null; - - public SimpleAdapter() - { - } - - public SimpleAdapter(Action callOnSend) - { - _callOnSend = callOnSend; - } - - public SimpleAdapter(Action callOnUpdate) - { - _callOnUpdate = callOnUpdate; - } - - public SimpleAdapter(Action callOnDelete) - { - _callOnDelete = callOnDelete; - } - - public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) - { - Assert.NotNull(reference); // SimpleAdapter.deleteActivity: missing reference - _callOnDelete?.Invoke(reference); - return Task.CompletedTask; - } - - public override Task SendActivitiesAsync(ITurnContext turnContext, IActivity[] activities, CancellationToken cancellationToken) - { - Assert.NotNull(activities); // SimpleAdapter.deleteActivity: missing reference - Assert.True(activities.Count() > 0, "SimpleAdapter.sendActivities: empty activities array."); - - _callOnSend?.Invoke(activities); - List responses = new List(); - - foreach (var activity in activities) - { - responses.Add(new ResourceResponse(activity.Id)); - } - - return Task.FromResult(responses.ToArray()); - } - - public override Task UpdateActivityAsync(ITurnContext turnContext, IActivity activity, CancellationToken cancellationToken) - { - Assert.NotNull(activity); //SimpleAdapter.updateActivity: missing activity - _callOnUpdate?.Invoke(activity); - return Task.FromResult(new ResourceResponse(activity.Id)); // echo back the Id - } - - public async Task ProcessRequest(Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken) - { - using (var ctx = new TurnContext(this, activity)) - { - await this.RunPipelineAsync(ctx, callback, cancellationToken); - } - } - } -} From 59dc8e7a5729e259afbc1f89928f0b8fbe6970be Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 10:08:14 -0600 Subject: [PATCH 21/60] Renamed BotFrameworkSkillHandler to ProxyChannelApiHandler --- .../DialogExtensions.cs | 5 ++-- ...llHandler.cs => ProxyChannelApiHandler.cs} | 12 ++++----- .../AspNetCore/ChannelApiController.cs | 2 +- .../DialogExtensionsTests.cs | 4 +-- ...ests.cs => ProxyChannelApiHandlerTests.cs} | 27 +++++++++---------- 5 files changed, 23 insertions(+), 27 deletions(-) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{Compat/BotFrameworkSkillHandler.cs => ProxyChannelApiHandler.cs} (97%) rename src/tests/Microsoft.Agents.Client.Tests/{BotFrameworkSkillHandlerTests.cs => ProxyChannelApiHandlerTests.cs} (93%) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index e34adbef..280fbeb1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -12,7 +12,6 @@ using Microsoft.Agents.Authentication; using Microsoft.Agents.Telemetry; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.State; namespace Microsoft.Agents.BotBuilder.Dialogs @@ -186,7 +185,7 @@ private static bool SendEoCToParent(ITurnContext turnContext) { // EoC Activities returned by skills are bounced back to the bot by SkillHandler. // In those cases we will have a SkillConversationReference instance in state. - var skillConversationReference = turnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey); + var skillConversationReference = turnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey); if (skillConversationReference != null) { // If the skillConversationReference.OAuthScope is for one of the supported channels, we are at the root and we should not send an EoC. @@ -201,7 +200,7 @@ private static bool SendEoCToParent(ITurnContext turnContext) private static bool IsFromParentToSkill(ITurnContext turnContext) { - if (turnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey) != null) + if (turnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey) != null) { return false; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs index dcde3937..b723c216 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/BotFrameworkSkillHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs @@ -15,15 +15,13 @@ using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Connector; -namespace Microsoft.Agents.BotBuilder.Compat +namespace Microsoft.Agents.BotBuilder { /// - /// This is the Bot Framework SDK implementation of IChannelApiHandler for handling Skill requests. + /// This IChannelApiHandler is primarily used when calling another bot using DeliveryModes.Normal, and forwarding most + /// bot replies to the originating channel. /// - /// - /// This is for backward compatibility for Bot Framework Skills. - /// - public class BotFrameworkSkillHandler : IChannelApiHandler + public class ProxyChannelApiHandler : IChannelApiHandler { public static readonly string SkillConversationReferenceKey = "Microsoft.Agents.BotBuilder.Skills.SkillConversationReference"; @@ -32,7 +30,7 @@ public class BotFrameworkSkillHandler : IChannelApiHandler private readonly IConversationIdFactory _conversationIdFactory; private readonly ILogger _logger; - public BotFrameworkSkillHandler( + public ProxyChannelApiHandler( IChannelAdapter adapter, IBot bot, IConversationIdFactory conversationIdFactory, diff --git a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs index 355043d3..a88032e6 100644 --- a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs +++ b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs @@ -13,7 +13,7 @@ namespace Microsoft.Agents.Hosting.AspNetCore /// This contains the routes for the ChannelAPI. These are the endpoints that /// ConnectorClient uses in the case of a bot-to-bot or Bot Framework Skill. /// The implementation of this is via . - /// See the Microsoft.Agents.BotBuilder.BotFrameworkSkillHandler class for the Bot Framework Root bot handling of these. + /// See the Microsoft.Agents.BotBuilder.ProxyChannelApiHandler class for an example of this for Dialogs and Skills. /// /// /// diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 8e50218c..1d4bccfd 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -190,14 +190,14 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc { // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. // This emulates a response coming to a root bot through SkillHandler. - turnContext.StackState.Set(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); + turnContext.StackState.Set(ProxyChannelApiHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = AuthenticationConstants.BotFrameworkScope }); } if (testCase == FlowTestCase.MiddleSkill) { // Simulate the SkillConversationReference with a parent Bot ID stored in TurnState. // This emulates a response coming to a skill from another skill through SkillHandler. - turnContext.StackState.Set(BotFrameworkSkillHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); + turnContext.StackState.Set(ProxyChannelApiHandler.SkillConversationReferenceKey, new BotConversationReference { OAuthScope = _parentBotId }); } } diff --git a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs b/src/tests/Microsoft.Agents.Client.Tests/ProxyChannelApiHandlerTests.cs similarity index 93% rename from src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs rename to src/tests/Microsoft.Agents.Client.Tests/ProxyChannelApiHandlerTests.cs index 2a55cf90..1484dcfb 100644 --- a/src/tests/Microsoft.Agents.Client.Tests/BotFrameworkSkillHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Client.Tests/ProxyChannelApiHandlerTests.cs @@ -20,13 +20,12 @@ using Moq; using Xunit; using Xunit.Abstractions; -using Microsoft.Agents.BotBuilder.Compat; namespace Microsoft.Agents.Client.Tests { - public class BotFrameworkSkillHandlerTests + public class ProxyChannelApiHandlerTests { - private ILogger _logger = null; + private ILogger _logger = null; private static readonly string TestSkillId = Guid.NewGuid().ToString("N"); private static readonly string TestAuthHeader = string.Empty; // Empty since claims extraction is being mocked private static readonly ChannelAccount TestMember = new ChannelAccount() @@ -36,7 +35,7 @@ public class BotFrameworkSkillHandlerTests }; - public BotFrameworkSkillHandlerTests(ITestOutputHelper output) + public ProxyChannelApiHandlerTests(ITestOutputHelper output) { IConfiguration config = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) @@ -52,7 +51,7 @@ public BotFrameworkSkillHandlerTests(ITestOutputHelper output) }) .AddConfiguration(config.GetSection("Logging")) .AddProvider(new TraceConsoleLoggingProvider(output))); - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); } @@ -71,13 +70,13 @@ public async Task TestSendAndReplyToConversationAsync(string activityType, strin var conversationId = await mockObjects.CreateAndApplyConversationIdAsync(activity); // Act - var sut = new BotFrameworkSkillHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); + var sut = new ProxyChannelApiHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); var response = replyToId == null ? await sut.OnSendToConversationAsync(mockObjects.CreateTestClaims(), conversationId, activity) : await sut.OnReplyToActivityAsync(mockObjects.CreateTestClaims(), conversationId, replyToId, activity); // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey)); // Assert based on activity type, if (activityType == ActivityTypes.Message) @@ -126,13 +125,13 @@ public async Task TestCommandActivities(string commandActivityType, string name, var conversationId = await mockObjects.CreateAndApplyConversationIdAsync(activity); // Act - var sut = new BotFrameworkSkillHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); + var sut = new ProxyChannelApiHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); var response = replyToId == null ? await sut.OnSendToConversationAsync(mockObjects.CreateTestClaims(), conversationId, activity) : await sut.OnReplyToActivityAsync(mockObjects.CreateTestClaims(), conversationId, replyToId, activity); // Assert // Assert the turnContext. Assert.Equal($"{CallerIdConstants.BotToBotPrefix}{TestSkillId}", mockObjects.TurnContext.Activity.CallerId); - Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey)); if (name.StartsWith("application/")) { // Should be sent to the channel and not to the bot. @@ -163,11 +162,11 @@ public async Task TestDeleteActivityAsync() var activityToDelete = Guid.NewGuid().ToString(); // Act - var sut = new BotFrameworkSkillHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); + var sut = new ProxyChannelApiHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); await sut.OnDeleteActivityAsync(mockObjects.CreateTestClaims(), conversationId, activityToDelete); // Assert - Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey)); Assert.Equal(activityToDelete, mockObjects.ActivityIdToDelete); } @@ -181,12 +180,12 @@ public async Task TestUpdateActivityAsync() var activityToUpdate = Guid.NewGuid().ToString(); // Act - var sut = new BotFrameworkSkillHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); + var sut = new ProxyChannelApiHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); var response = await sut.OnUpdateActivityAsync(mockObjects.CreateTestClaims(), conversationId, activityToUpdate, activity); // Assert Assert.Equal("resourceId", response.Id); - Assert.NotNull(mockObjects.TurnContext.StackState.Get(BotFrameworkSkillHandler.SkillConversationReferenceKey)); + Assert.NotNull(mockObjects.TurnContext.StackState.Get(ProxyChannelApiHandler.SkillConversationReferenceKey)); Assert.Equal(activityToUpdate, mockObjects.TurnContext.Activity.Id); Assert.Equal(activity.Text, mockObjects.UpdateActivity.Text); } @@ -200,7 +199,7 @@ public async Task TestGetConversationMemberAsync() var conversationId = await mockObjects.CreateAndApplyConversationIdAsync(activity); // Act - var sut = new BotFrameworkSkillHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); + var sut = new ProxyChannelApiHandler(mockObjects.Adapter.Object, mockObjects.Bot.Object, mockObjects.ConversationIdFactory); var member = await sut.OnGetConversationMemberAsync(mockObjects.CreateTestClaims(), TestMember.Id, conversationId); // Assert From e7653898accf181273b44554a8943af51b01150f Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 10:50:22 -0600 Subject: [PATCH 22/60] Removed used of CastTo in Dialogs --- .../Extensions.cs | 24 ------------------- .../Prompts/ActivityPrompt.cs | 2 +- .../Prompts/OAuthPrompt.cs | 4 ++-- .../Prompts/Prompt.cs | 2 +- .../Prompts/PromptValidatorContext.cs | 2 +- .../WaterfallDialog.cs | 4 ++-- 6 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Extensions.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Extensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Extensions.cs deleted file mode 100644 index 466462e3..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Extensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed under the MIT License. -// Copyright (c) Microsoft Corporation. All rights reserved. - -using Microsoft.Agents.Core.Serialization; - -namespace Microsoft.Agents.BotBuilder.Dialogs -{ - /// - /// Extension method on object . - /// - internal static class Extensions - { - /// - /// Extension Method on object to cast to type T to support TypeNameHandling.None during storage serialization. - /// - /// object to cast. - /// type to which object should be casted. - /// T. - public static T CastTo(this object obj) - { - return ProtocolJsonSerializer.ToObject(obj); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs index 753e768a..a5c2ef1e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/ActivityPrompt.cs @@ -122,7 +122,7 @@ public override async Task ContinueDialogAsync(DialogContext d // Increment attempt count // Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859 - state[Prompt.AttemptCountKey] = state[Prompt.AttemptCountKey].CastTo() + 1; + state[Prompt.AttemptCountKey] = (int) state[Prompt.AttemptCountKey] + 1; // Validate the return value var isValid = false; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 4337e2ab..4f094dfa 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -179,7 +179,7 @@ public override async Task ContinueDialogAsync(DialogContext d // Check for timeout var state = dc.ActiveDialog.State; - var expires = state[PersistedExpires].CastTo(); + var expires = (DateTime) state[PersistedExpires]; var isMessage = dc.Context.Activity.Type == ActivityTypes.Message; try @@ -215,7 +215,7 @@ public override async Task ContinueDialogAsync(DialogContext d // Increment attempt count // Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859 - promptState[Prompt.AttemptCountKey] = promptState[Prompt.AttemptCountKey].CastTo() + 1; + promptState[Prompt.AttemptCountKey] = (int) promptState[Prompt.AttemptCountKey] + 1; // Validate the return value var isValid = recognized.Succeeded; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs index 64d8e6a0..66e1fa12 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/Prompt.cs @@ -137,7 +137,7 @@ public override async Task ContinueDialogAsync(DialogContext d // Increment attempt count // Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859 - state[AttemptCountKey] = state[AttemptCountKey].CastTo() + 1; + state[AttemptCountKey] = (int) state[AttemptCountKey] + 1; // Validate the return value var isValid = false; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs index 99da174f..598dc540 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/PromptValidatorContext.cs @@ -57,7 +57,7 @@ public int AttemptCount return 0; } - return State[Prompt.AttemptCountKey].CastTo(); + return (int) State[Prompt.AttemptCountKey]; } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs index d081432e..98ec8450 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/WaterfallDialog.cs @@ -144,7 +144,7 @@ public override async Task ResumeDialogAsync(DialogContext dc, // Increment step index and run step var state = dc.ActiveDialog.State; - var index = state[StepIndex].CastTo(); + var index = (int) state[StepIndex]; return await RunStepAsync(dc, index + 1, reason, result, cancellationToken).ConfigureAwait(false); } @@ -164,7 +164,7 @@ public override Task EndDialogAsync(ITurnContext turnContext, DialogInstance ins var state = new Dictionary((Dictionary)instance.State); // Create step context - var index = state[StepIndex].CastTo(); + var index = (int) state[StepIndex]; var stepName = WaterfallStepName(index); var instanceId = state[PersistedInstanceId]?.ToString(); From ddc7dee6d4a875f229f8e9bf214b6466f09e9e68 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 13:09:25 -0600 Subject: [PATCH 23/60] Moved InputFile back to core --- .../Microsoft.Agents.BotBuilder}/App/IInputFileDownloader.cs | 3 +-- .../Microsoft.Agents.BotBuilder}/App/InputFile.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/libraries/{Extensions/Microsoft.Agents.Extensions.Teams => BotBuilder/Microsoft.Agents.BotBuilder}/App/IInputFileDownloader.cs (91%) rename src/libraries/{Extensions/Microsoft.Agents.Extensions.Teams => BotBuilder/Microsoft.Agents.BotBuilder}/App/InputFile.cs (95%) diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs similarity index 91% rename from src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs index 6e9052d2..a7641dba 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/IInputFileDownloader.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.Extensions.Teams.App +namespace Microsoft.Agents.BotBuilder.App { /// /// A plugin responsible for downloading files relative to the current user's input. diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/InputFile.cs similarity index 95% rename from src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/InputFile.cs index 119d589a..bc0fb5de 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/InputFile.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/InputFile.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.Extensions.Teams.App +namespace Microsoft.Agents.BotBuilder.App { /// /// Represents an upload file From 0d196d037a9b110131fa272b0b7f0dc0e6cbad13 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 13:45:13 -0600 Subject: [PATCH 24/60] Corrected another DictionaryOfObjectConverter deserialization miss --- .../Converters/DictionaryOfObjectConverter.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs index 3e6be407..8eb8aaae 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/Converters/DictionaryOfObjectConverter.cs @@ -82,7 +82,43 @@ internal class DictionaryOfObjectConverter : JsonConverter(out var intValue)) + { + objValue = intValue; + } + break; + + case JsonValueKind.String: + if (valValue.TryGetValue(out var dateValue)) + { + objValue = dateValue; + } + else if (valValue.TryGetValue(out var strValue)) + { + objValue = strValue; + } + break; + + case JsonValueKind.True: + case JsonValueKind.False: + if (valValue.TryGetValue(out var boolValue)) + { + objValue = boolValue; + } + break; + + default: + objValue = itemValue; break; + } + } + value.Add(keyString, objValue); } } throw new JsonException($"JSON did not contain the end of {typeToConvert.FullName}!"); From 7fc37d6c3c09faf507dd86a0926fc662b7fd5592 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 16:02:10 -0600 Subject: [PATCH 25/60] Teams model roundtrip tests (initial setup) --- .../Models/TeamsChannelDataSettings.cs | 3 +- .../TeamsChannelDataSettingsConverter.cs | 69 +++++++++++++++++++ .../Serialization/SerializerExtensions.cs | 2 +- ...osoft.Agents.Extensions.Teams.Tests.csproj | 6 ++ .../Model/AppBasedLinkQueryTests.cs | 26 +++++++ .../Model/BotConfigAuthTests.cs | 48 ++++++++++++- .../Model/FileConsentCardTests.cs | 25 +++++++ .../Model/LoadTestJson.cs | 16 +++++ .../Model/TeamsChannelAccountTests.cs | 27 ++++++++ .../Model/TeamsChannelDataTests.cs | 30 ++++++++ .../Model/TestJson/AppBasedLinkQuery.json | 1 + .../Model/TestJson/BotConfigAuth.json | 1 + .../Model/TestJson/FileConsentCard.json | 1 + .../Model/TestJson/TeamsChannelAccount.json | 1 + .../Model/TestJson/TeamsChannelData.json | 1 + 15 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataSettingsConverter.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/LoadTestJson.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/AppBasedLinkQuery.json create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/BotConfigAuth.json create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/FileConsentCard.json create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelAccount.json create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelData.json diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs index a72ab3f6..067bbccf 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Models/TeamsChannelDataSettings.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Text.Json; namespace Microsoft.Agents.Extensions.Teams.Models { @@ -41,7 +42,7 @@ public TeamsChannelDataSettings(ChannelInfo channel = default) /// the JSON object is deserialized, but are instead stored in this property. Such properties /// will be written to a JSON object when the instance is serialized. #pragma warning disable CA2227 // Collection properties should be read only - public IDictionary AdditionalProperties { get; set; } + public IDictionary Properties { get; set; } = new Dictionary(); #pragma warning restore CA2227 // Collection properties should be read only } } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataSettingsConverter.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataSettingsConverter.cs new file mode 100644 index 00000000..5784d2cc --- /dev/null +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/Converters/TeamsChannelDataSettingsConverter.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Core.Serialization.Converters; +using Microsoft.Agents.Extensions.Teams.Models; +using System.Collections; +using System.Reflection; +using System.Text.Json; + +namespace Microsoft.Agents.Extensions.Teams.Serialization.Converters +{ + internal class TeamsChannelDataSettingsConverter : ConnectorConverter + { + protected override bool TryReadCollectionProperty(ref Utf8JsonReader reader, TeamsChannelDataSettings value, string propertyName, JsonSerializerOptions options) + { + PropertyInfo propertyInfo = typeof(TeamsChannelDataSettings).GetProperty(propertyName); + if (propertyInfo != null && propertyInfo.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType)) + { + return true; + } + return false; + } + + protected override bool TryReadGenericProperty(ref Utf8JsonReader reader, TeamsChannelDataSettings value, string propertyName, JsonSerializerOptions options) + { + return false; + } + + protected override void ReadExtensionData(ref Utf8JsonReader reader, TeamsChannelDataSettings value, string propertyName, JsonSerializerOptions options) + { + var extensionData = System.Text.Json.JsonSerializer.Deserialize(ref reader, options); + value.Properties.Add(propertyName, extensionData); + } + + protected override bool TryReadExtensionData(ref Utf8JsonReader reader, TeamsChannelDataSettings value, string propertyName, JsonSerializerOptions options) + { + if (propertyName.Equals(nameof(value.Properties))) + { + var propertyValue = System.Text.Json.JsonSerializer.Deserialize(ref reader, options); + + foreach (var element in propertyValue.ToJsonElements()) + { + value.Properties.Add(element.Key, element.Value); + } + + return true; + } + + return false; + } + + protected override bool TryWriteExtensionData(Utf8JsonWriter writer, TeamsChannelDataSettings value, string propertyName) + { + if (propertyName.Equals(nameof(value.Properties))) + { + foreach (var extensionData in value.Properties) + { + writer.WritePropertyName(extensionData.Key); + extensionData.Value.WriteTo(writer); + } + + return true; + } + + return false; + } + } +} diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs index 911154c7..2645108f 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Serialization/SerializerExtensions.cs @@ -20,7 +20,7 @@ public static JsonSerializerOptions ApplyTeamsOptions(this JsonSerializerOptions options.Converters.Add(new TaskModuleContinueResponseConverter()); options.Converters.Add(new TaskModuleMessageResponseConverter()); options.Converters.Add(new MessagingExtensionAttachmentConverter()); - + options.Converters.Add(new TeamsChannelDataSettingsConverter()); return options; } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj index 737ac688..2a3863a3 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj @@ -12,6 +12,12 @@ + + + PreserveNewest + + + diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs index 0f4c40e0..54d66397 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/AppBasedLinkQueryTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; +using System.IO; using Xunit; namespace Microsoft.Agents.Extensions.Teams.Tests.Model @@ -33,5 +35,29 @@ public void AppBasedLinkQueryInitsWithNoArgs() Assert.NotNull(appBasedLinkQuery); Assert.IsType(appBasedLinkQuery); } + + [Fact] + public void AppBasedLinkQueryRoundTrip() + { + var url = "http://example.com"; + var state = "magicCode"; + + var appBasedLinkQuery = new AppBasedLinkQuery(url) + { + State = state + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(appBasedLinkQuery); + + // Out + var json = ProtocolJsonSerializer.ToJson(appBasedLinkQuery); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs index 448ccb4a..e6003358 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/BotConfigAuthTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; @@ -11,11 +13,55 @@ public class BotConfigAuthTests [Fact] public void BotConfigAuthInitsWithNoArgs() { - var botConfigAuthResponse = new BotConfigAuth(); + var botConfigAuthResponse = new BotConfigAuth() + { + SuggestedActions = new SuggestedActions() + { + To = ["id1", "id2"], + + } + }; Assert.NotNull(botConfigAuthResponse); Assert.IsType(botConfigAuthResponse); Assert.Equal("auth", botConfigAuthResponse.Type); } + + [Fact] + public void BotConfigAuthRoundTrip() + { + var botConfigAuth = new BotConfigAuth() + { + SuggestedActions = new SuggestedActions() + { + To = ["id1", "id2"], + Actions = [ + new CardAction() + { + Type = "type", + Title = "title", + Image = "image", + ImageAltText = "imageAltText", + Text = "text", + DisplayText = "displayText", + Value = new { value = "value" }, + ChannelData = new { channelData = "channelData"} + } + ] + } + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(botConfigAuth); + + // Out + var json = ProtocolJsonSerializer.ToJson(botConfigAuth); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs index 45230d43..64ff866b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/FileConsentCardTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; @@ -34,5 +35,29 @@ public void FileConsentCardInitsWithNoArgs() Assert.NotNull(fileConsentCard); Assert.IsType(fileConsentCard); } + + [Fact] + public void FileConsentCardRoundTrip() + { + var fileConsentCard = new FileConsentCard() + { + Description = "description", + SizeInBytes = 1, + AcceptContext = new { acceptContext = "acceptContext" }, + DeclineContext = new { declineContext = "acceptContext" } + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(fileConsentCard); + + // Out + var json = ProtocolJsonSerializer.ToJson(fileConsentCard); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/LoadTestJson.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/LoadTestJson.cs new file mode 100644 index 00000000..f39da785 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/LoadTestJson.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; + +namespace Microsoft.Agents.Extensions.Teams.Tests.Model +{ + internal static class LoadTestJson + { + public static string LoadJson(object obj, string variant = null) + { + var filename = variant == null ? obj.GetType().Name : $"{obj.GetType().Name}_{variant}"; + return File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "Model/TestJson", $"{filename}.json")); + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs index d9939373..35cc419f 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelAccountTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; @@ -69,5 +70,31 @@ public void TeamChannelAccountInitsWithNoArgs() Assert.NotNull(teamsChannelAccount); Assert.IsType(teamsChannelAccount); } + + [Fact] + public void TeamChannelAccountRoundTrip() + { + var teamsChannelData = new TeamsChannelAccount() + { + GivenName = "givenName", + Surname = "surname", + Email = "email", + UserPrincipalName = "userPrincipalName", + TenantId = "tenantId", + UserRole = "userRole", + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(teamsChannelData); + + // Out + var json = ProtocolJsonSerializer.ToJson(teamsChannelData); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs index 94e55acd..4b2a95c2 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsChannelDataTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Text.Json; using Microsoft.Agents.Core; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; @@ -111,5 +112,34 @@ public void Activity_TeamsChannelData() Assert.True(channelData.Properties.ContainsKey(TestKey)); Assert.Equal(TestValue, channelData.Properties[TestKey].ToString()); } + + [Fact] + public void TeamsChannelDataRoundTrip() + { + var teamsChannelData = new TeamsChannelData() + { + Channel = new ChannelInfo() { Id = "channel_id", Name = "channel_name", Type = "channel_type" }, + EventType = "eventType", + Team = new TeamInfo() { Id = "team_id", Name = "team_name", AadGroupId = "aadgroupid_id" }, + Notification = new NotificationInfo() { Alert = true, AlertInMeeting = true, ExternalResourceUrl = "resourceUrl" }, + Tenant = new TenantInfo() { Id = "tenant_id" }, + Meeting = new TeamsMeetingInfo() { Id = "meeting_id" }, + Settings = new TeamsChannelDataSettings() { SelectedChannel = new ChannelInfo() { Id = "channel_id", Name = "channel_name", Type = "channel_type" }, Properties = ProtocolJsonSerializer.ToJsonElements(new { prop1 = "prop1"}) }, + OnBehalfOf = [ new OnBehalfOf() { DisplayName = "displayName", ItemId = 1, MentionType = "mentionType", Mri = "mri"}], + Properties = ProtocolJsonSerializer.ToJsonElements(new { prop1 = "root_prop1" }) + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(teamsChannelData); + + // Out + var json = ProtocolJsonSerializer.ToJson(teamsChannelData); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/AppBasedLinkQuery.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/AppBasedLinkQuery.json new file mode 100644 index 00000000..69cd8cf3 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/AppBasedLinkQuery.json @@ -0,0 +1 @@ +{"url":"http://example.com","state":"magicCode"} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/BotConfigAuth.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/BotConfigAuth.json new file mode 100644 index 00000000..eecf008f --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/BotConfigAuth.json @@ -0,0 +1 @@ +{"type":"auth","suggestedActions":{"to":["id1","id2"],"actions":[{"type":"type","title":"title","image":"image","imageAltText":"imageAltText","text":"text","displayText":"displayText","value":{"value":"value"},"channelData":{"channelData":"channelData"}}]}} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/FileConsentCard.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/FileConsentCard.json new file mode 100644 index 00000000..6984bad6 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/FileConsentCard.json @@ -0,0 +1 @@ +{"description":"description","sizeInBytes":1,"acceptContext":{"acceptContext":"acceptContext"},"declineContext":{"declineContext":"acceptContext"}} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelAccount.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelAccount.json new file mode 100644 index 00000000..1db78a7b --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelAccount.json @@ -0,0 +1 @@ +{"givenName":"givenName","surname":"surname","email":"email","userPrincipalName":"userPrincipalName","userRole":"userRole","tenantId":"tenantId","properties":{}} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelData.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelData.json new file mode 100644 index 00000000..c322c852 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/TeamsChannelData.json @@ -0,0 +1 @@ +{"channel":{"id":"channel_id","name":"channel_name","type":"channel_type"},"eventType":"eventType","team":{"id":"team_id","name":"team_name","aadGroupId":"aadgroupid_id"},"notification":{"alert":true,"alertInMeeting":true,"externalResourceUrl":"resourceUrl"},"tenant":{"id":"tenant_id"},"meeting":{"id":"meeting_id"},"settings":{"selectedChannel":{"id":"channel_id","name":"channel_name","type":"channel_type"},"prop1":"prop1"},"onBehalfOf":[{"itemId":1,"mentionType":"mentionType","mri":"mri","displayName":"displayName"}],"prop1":"root_prop1"} \ No newline at end of file From e2666d3b0629cbcc8d0920103be9e8dba26f6d3c Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 16:18:49 -0600 Subject: [PATCH 26/60] MessagingExtensionActionResponse roundtrip serialize tests --- .../MessagingExtensionActionResponseTests.cs | 49 ++++++++++++++++++- .../MessagingExtensionActionResponse.json | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionActionResponse.json diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs index e5591650..23cdcad8 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections.Generic; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; +using Newtonsoft.Json.Linq; +using System; using Xunit; +using static System.Net.Mime.MediaTypeNames; namespace Microsoft.Agents.Extensions.Teams.Tests.Model { @@ -36,5 +40,48 @@ public void MessagingExtensionActionResponseInitsWithNoArgs() Assert.NotNull(msgExtActionResponse); Assert.IsType(msgExtActionResponse); } + + [Fact] + public void MessagingExtensionActionResponseRoundTrip() + { + var task = new TaskModuleResponseBase("message") { Properties = ProtocolJsonSerializer.ToJsonElements(new { prop1 = "prop1" }) }; + var composeExtension = new MessagingExtensionResult() + { + AttachmentLayout = "attachmentLayout", + Type = "type", + Attachments = [new MessagingExtensionAttachment() { }], + SuggestedActions = new MessagingExtensionSuggestedAction() { Actions = [new CardAction() + { + Type = "type", + Title = "title", + Image = "image", + ImageAltText = "imageAltText", + Text = "text", + DisplayText = "displayText", + Value = new { value = "value" }, + ChannelData = new { channelData = "channelData"} + }] }, + Text = "text", + ActivityPreview = new Activity() + }; + + var msgExtActionResponse = new MessagingExtensionActionResponse(task, composeExtension) + { + CacheInfo = new CacheInfo() { CacheDuration = 1, CacheType = "cacheType"} + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(msgExtActionResponse); + + // Out + var json = ProtocolJsonSerializer.ToJson(msgExtActionResponse); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } + } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionActionResponse.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionActionResponse.json new file mode 100644 index 00000000..9d4e55a7 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionActionResponse.json @@ -0,0 +1 @@ +{"task":{"type":"message","prop1":"prop1"},"composeExtension":{"attachmentLayout":"attachmentLayout","type":"type","attachments":[{"properties":{}}],"suggestedActions":{"actions":[{"type":"type","title":"title","image":"image","imageAltText":"imageAltText","text":"text","displayText":"displayText","value":{"value":"value"},"channelData":{"channelData":"channelData"}}]},"text":"text","activityPreview":{}},"cacheInfo":{"cacheType":"cacheType","cacheDuration":1}} \ No newline at end of file From 997f2baeee776e826a8cb8188cf5c9a06d2a2d31 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 17:32:38 -0600 Subject: [PATCH 27/60] Application merge cleanup --- .../App/State/IMemory.cs | 42 -- .../App/State/ITurnState.cs | 52 -- .../App/State/MemoryFork.cs | 117 ----- .../App/State/Record.cs | 83 --- .../App/State/TempState.cs | 104 ---- .../App/State/TurnState.cs | 473 ------------------ .../App/State/TurnStateEntry.cs | 89 ---- .../messaging.echoBot/EchoBotApplication.cs | 2 - 8 files changed, 962 deletions(-) delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs deleted file mode 100644 index 7ddf6f22..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/IMemory.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// Represents a memory, a key-value store that can be used to store and retrieve values. - /// - public interface IMemory - { - /// - /// Deletes a value from the memory. - /// - /// Path to the value to delete in the form of `[scope].property`. - /// If scope is omitted, the value is deleted from the temporary scope. - void DeleteValue(string path); - - /// - /// Checks if a value exists in the memory. - /// - /// Path to the value to check in the form of `[scope].property`. - /// If scope is omitted, the value is checked in the temporary scope. - /// True if the value exists, false otherwise. - bool HasValue(string path); - - /// - /// Retrieves a value from the memory. - /// - /// Path to the value to retrieve in the form of `[scope].property`. - /// If scope is omitted, the value is retrieved from the temporary scope. - /// The value or undefined if not found. - object? GetValue(string path); - - /// - /// Assigns a value to the memory. - /// - /// Path to the value to assign in the form of `[scope].property`. - /// If scope is omitted, the value is assigned to the temporary scope. - /// Value to assign. - void SetValue(string path, object value); - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs deleted file mode 100644 index 9185ebb8..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/ITurnState.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Storage; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// The turn state interface. - /// - /// The conversation state class. - /// The user state class. - /// The temp state class. - public interface ITurnState - where TConversationState : class - where TUserState : class - where TTempState : TempState - { - /// - /// Gets the conversation state. - /// - public TConversationState? Conversation { get; } - - /// - /// Gets the user state. - /// - public TUserState? User { get; } - - /// - /// Gets the temp state. - /// - public TTempState? Temp { get; } - - /// - /// Loads all of the state scopes for the current turn. - /// - /// Optional. Storage provider to load state scopes from. - /// Context for the current turn of conversation with the user. - /// - /// True if the states need to be loaded. - public Task LoadStateAsync(IStorage? storage, ITurnContext turnContext, CancellationToken cancellationToken = default); - - /// - /// Saves all of the state scopes for the current turn. - /// - /// Optional. Storage provider to save state scopes to. - /// Context for the current turn of conversation with the user. - public Task SaveStateAsync(IStorage? storage, ITurnContext turnContext); - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs deleted file mode 100644 index bd75f135..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/MemoryFork.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - - /// - /// Forks an existing memory. - /// A memory fork is a memory that is a copy of another memory, but can be modified without affecting the original memory. - /// - public class MemoryFork : IMemory - { - private readonly Dictionary _fork = new(); - private readonly IMemory? _memory; - - /// - /// Creates a new `MemoryFork` instance. - /// - /// Memory to fork. - public MemoryFork(IMemory? memory = null) - { - _memory = memory; - } - - /// - /// Deletes a value from the memory. Only forked values will be deleted. - /// - /// Path to the value to delete in the form of `[scope].property`. - /// If scope is omitted, the value is deleted from the temporary scope. - public void DeleteValue(string path) - { - (string scope, string name) = GetScopeAndName(path); - if (_fork.ContainsKey(scope) && _fork[scope].ContainsKey(name)) - { - _fork[scope].Remove(name); - } - } - - /// - /// Retrieves a value from the memory. The forked memory is checked first, then the original memory. - /// - /// Path to the value to retrieve in the form of `[scope].property`. - /// If scope is omitted, the value is retrieved from the temporary scope. - /// The value or undefined if not found. - public object? GetValue(string path) - { - (string scope, string name) = GetScopeAndName(path); - if (_fork.ContainsKey(scope)) - { - if (_fork[scope].ContainsKey(name)) - { - return _fork[scope][name]; - } - } - - return _memory?.GetValue(path); - } - - /// - /// Checks if a value exists in the memory. The forked memory is checked first, then the original memory. - /// - /// Path to the value to check in the form of `[scope].property`. - /// If scope is omitted, the value is checked in the temporary scope. - /// True if the value exists, false otherwise. - public bool HasValue(string path) - { - (string scope, string name) = GetScopeAndName(path); - if (_fork.ContainsKey(scope)) - { - return _fork[scope].ContainsKey(name); - } - - if (_memory != null) - { - return _memory.HasValue(path); - } - - return false; - } - - /// - /// Assigns a value to the memory. The value is assigned to the forked memory. - /// - /// Path to the value to assign in the form of `[scope].property`. - /// If scope is omitted, the value is assigned to the temporary scope. - /// Value to assign. - public void SetValue(string path, object value) - { - (string scope, string name) = GetScopeAndName(path); - if (!_fork.ContainsKey(scope)) - { - _fork[scope] = new(); - } - - _fork[scope][name] = value; - } - - private (string, string) GetScopeAndName(string path) - { - List parts = path.Split('.').ToList(); - - if (parts.Count > 2) - { - throw new InvalidOperationException($"Invalid state path: {path}"); - } - if (parts.Count == 1) - { - parts.Insert(0, "temp"); - } - return (parts[0], parts[1]); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs deleted file mode 100644 index aa40732a..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/Record.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Core.Serialization; -using System; -using System.Collections.Generic; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// The class representing a record. - /// - public class Record : Dictionary - { - /// - /// Tries to get the value from the dictionary. - /// - /// Type of the value - /// key to look for - /// value associated with key - /// True if a value of given type is associated with key. - /// - public bool TryGetValue(string key, out T value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(key); - - if (base.TryGetValue(key, out object entry)) - { - if (entry is T castedEntry) - { - value = castedEntry; - return true; - }; - - //throw new InvalidCastException($"Failed to cast generic object to type '{typeof(T)}'"); - value = ProtocolJsonSerializer.ToObject(entry); - return true; - } - -#pragma warning disable CS8601 // Possible null reference assignment. - value = default; -#pragma warning restore CS8601 // Possible null reference assignment. - - return false; - } - - /// - /// Gets the value from the dictionary. - /// - /// Type of the value - /// key to look for - /// The value associated with the key - public T? Get(string key) - { - ArgumentException.ThrowIfNullOrWhiteSpace(key); - - if (TryGetValue(key, out T value)) - { - return value; - } - else - { - return default; - }; - } - - /// - /// Sets value in the dictionary. - /// - /// Type of value - /// key to look for - /// value associated with key - public void Set(string key, T value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(key); - ArgumentNullException.ThrowIfNull(value); - -#pragma warning disable CS8601 // Possible null reference assignment. - this[key] = value; -#pragma warning restore CS8601 // Possible null reference assignment. - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs deleted file mode 100644 index a80b1b5d..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TempState.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Generic; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// Temporary state. - /// - /// - /// Inherit a new class from this base abstract class to strongly type the applications temp state. - /// - public class TempState : Record - { - /// - /// Name of the input property. - /// - public const string InputKey = "input"; - - /// - /// Name of the output property. - /// - public const string OutputKey = "output"; - - /// - /// Name of the action outputs property. - /// - public const string ActionOutputsKey = "actionOutputs"; - - /// - /// Name of the auth tokens property. - /// - public const string AuthTokenKey = "authTokens"; - - /// - /// Name of the duplicate token exchange property - /// - public const string DuplicateTokenExchangeKey = "duplicateTokenExchange"; - - /// - /// Name of the input files key - /// - public const string InputFilesKey = "inputFiles"; - - /// - /// Creates a new instance of the class. - /// - public TempState() : base() - { - this[InputKey] = string.Empty; - this[OutputKey] = string.Empty; - this[ActionOutputsKey] = new Dictionary(); - this[AuthTokenKey] = new Dictionary(); - this[DuplicateTokenExchangeKey] = false; - } - - /// - /// Input passed to an AI prompt - /// - public string Input - { - get => Get(InputKey)!; - set => Set(InputKey, value); - } - - // TODO: This is currently not used, should store AI prompt/function output here - /// - /// Output returned from an AI prompt or function - /// - public string Output - { - get => Get(OutputKey)!; - set => Set(OutputKey, value); - } - - /// - /// All outputs returned from the action sequence that was executed. - /// - public Dictionary ActionOutputs - { - get => Get>(ActionOutputsKey)!; - set => Set(ActionOutputsKey, value); - } - - /// - /// All tokens acquired after sign-in for current activity - /// - public Dictionary AuthTokens - { - get => Get>(AuthTokenKey)!; - set => Set(AuthTokenKey, value); - } - - /// - /// Whether current token exchange is a duplicate one - /// - public bool DuplicateTokenExchange - { - get => Get(DuplicateTokenExchangeKey)!; - set => Set(DuplicateTokenExchangeKey, value); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs deleted file mode 100644 index 0e5a3ad1..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnState.cs +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.Storage; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// Base class defining a collection of turn state scopes. - /// Developers can create a derived class that extends `TurnState` to add additional state scopes. - /// - public class TurnState : IMemory - { - private Dictionary _scopes = new(); - private bool _isLoaded = false; - private Task? _loadingTask = Task.FromResult(false); - - /// - /// Name of the conversation scope. - /// - public const string CONVERSATION_SCOPE = "conversation"; - - /// - /// Name of the user scope. - /// - public const string USER_SCOPE = "user"; - - /// - /// Name of the temp scope. - /// - public const string TEMP_SCOPE = "temp"; - - /// - /// Initializes a new instance of the class. - /// - public TurnState() - { - ScopeDefaults = new Dictionary(); - ScopeDefaults.Add(CONVERSATION_SCOPE, new Record()); - ScopeDefaults.Add(USER_SCOPE, new Record()); - } - - /// - /// The default values to initial for each scope. - /// - protected Dictionary ScopeDefaults; - - /// - /// Provides the current status of the load. - /// - /// Returns true if the scopes have been loaded, false otherwise. - public bool IsLoaded() { return _isLoaded; } - - /// - /// Returns the TurnStateEntry associated with the specified scope. - /// - /// The specified scope. - /// The state saved for the scope. - public TurnStateEntry? GetScope(string name) - { - try - { - return _scopes[name]; - } - catch (KeyNotFoundException) - { - return null; - } - } - - /// - /// Stores all the conversation-related state. - /// - public Record Conversation - { - get - { - TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - return scope.Value!; - } - set - { - ArgumentNullException.ThrowIfNull(value); - - TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - scope.Replace(value!); - } - } - - /// - /// Stores all the user related state. - /// - public Record User - { - get - { - TurnStateEntry? scope = GetScope(USER_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - return scope.Value!; - } - set - { - ArgumentNullException.ThrowIfNull(value); - - TurnStateEntry? scope = GetScope(USER_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - scope.Replace(value!); - } - } - - /// - /// Stores all the temporary state for the current turn. - /// - public TempState Temp - { - get - { - TurnStateEntry? scope = GetScope(TEMP_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - return (TempState)scope.Value!; - } - set - { - ArgumentNullException.ThrowIfNull(value); - - TurnStateEntry? scope = GetScope(TEMP_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - scope.Replace(value!); - } - } - - /// - /// Deletes the conversation state. - /// - public void DeleteConversationState() - { - TurnStateEntry? scope = GetScope(CONVERSATION_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - - scope.Delete(); - } - - /// - /// Deletes the temporary state. - /// - public void DeleteTempState() - { - TurnStateEntry? scope = GetScope(TEMP_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - scope.Delete(); - } - - /// - /// Deletes the user state - /// - public void DeleteUserState() - { - TurnStateEntry? scope = GetScope(USER_SCOPE); - if (scope == null) - { - throw new ArgumentException("TurnState hasn't been loaded. Call LoadStateAsync() first."); - } - scope.Delete(); - } - - /// - /// Deletes a value from the memory. - /// - /// Path to the value to delete in the form of `[scope].property`. - /// If scope is omitted, the value is deleted from the temporary scope. - public void DeleteValue(string path) - { - (TurnStateEntry scope, string name) = GetScopeAndName(path); - if (scope.Value!.ContainsKey(name)) - { - scope.Value.Remove(name); - } - } - - /// - /// Checks if a value exists in the memory. - /// - /// Path to the value to check in the form of `[scope].property`. - /// If scope is omitted, the value is checked in the temporary scope. - /// True if the value exists, false otherwise. - public bool HasValue(string path) - { - (TurnStateEntry scope, string name) = GetScopeAndName(path); - return scope.Value?.ContainsKey(name) == true; - } - - /// - /// Retrieves a value from the memory. - /// - /// Path to the value to retrieve in the form of `[scope].property`. - /// If scope is omitted, the value is retrieved from the temporary scope. - /// The value or undefined if not found. - public object? GetValue(string path) - { - (TurnStateEntry scope, string name) = GetScopeAndName(path); - - if (scope.Value?.ContainsKey(name) != true) - { - return null; - } - - return scope.Value[name]; - } - - /// - /// Assigns a value to the memory. - /// - /// Path to the value to assign in the form of `[scope].property`. - /// If scope is omitted, the value is assigned to the temporary scope. - /// Value to assign. - public void SetValue(string path, object value) - { - (TurnStateEntry scope, string name) = GetScopeAndName(path); - scope.Value![name] = value; - } - - /// - /// Loads all of the state scopes for the current turn. - /// - /// Optional. Storage provider to load state scopes from. - /// Context for the current turn of conversation with the user. - /// Optional. The cancellation token. - /// True if the states need to be loaded. - public async Task LoadStateAsync(IStorage? storage, ITurnContext turnContext, CancellationToken cancellationToken = default) - { - // Only load on first call - if (this._isLoaded) - { - return false; - } - - // Check for existing load operation - if (!(await this._loadingTask!)) - { - this._loadingTask = Task.Run(async () => - { - try - { - // Prevent additional load attempts - this._isLoaded = true; - - // Compute state keys - List keys = new(); - Dictionary scopes = OnComputeStorageKeys(turnContext); - foreach (KeyValuePair scope in scopes) - { - if (scopes.ContainsKey(scope.Key)) - { - keys.Add(scopes[scope.Key]); - } - } - - // Read items from storage provider (if configured) - IDictionary items; - if (storage != null) - { - items = await storage.ReadAsync(keys.Where(k => k != TEMP_SCOPE).ToArray(), cancellationToken); - } - else - { - items = new Dictionary(); - } - - // Create scopes for items - foreach (KeyValuePair scope in scopes) - { - if (scopes.ContainsKey(scope.Key)) - { - Record scopeDefault = ScopeDefaults.ContainsKey(scope.Key) ? ScopeDefaults[scope.Key] : new Record(); - string storageKey = scopes[scope.Key]; - object value = items.ContainsKey(storageKey) ? items[storageKey] : scopeDefault; - this._scopes[scope.Key] = new TurnStateEntry((value as Record)!, storageKey); - } - } - - // Add the temp scope - Record tempStateValue = ScopeDefaults.ContainsKey(TEMP_SCOPE) ? ScopeDefaults[TEMP_SCOPE] : new TempState(); - this._scopes[TEMP_SCOPE] = new TurnStateEntry(tempStateValue); - - // Clear loading task - this._isLoaded = true; - this._loadingTask = null; - return true; - } - catch (Exception ex) - { - this._loadingTask = null; - throw new InvalidOperationException($"Something went wrong when loading state: {ex.Message}", ex); - } - }); - } - - return this._loadingTask.Result; - } - - /// - /// Saves all of the state scopes for the current turn. - /// - /// Context for the current turn of conversation with the user. - /// Optional. Storage provider to save state scopes to. - public async Task SaveStateAsync(ITurnContext turnContext, IStorage? storage) - { - ArgumentNullException.ThrowIfNull(turnContext); - - // Check for existing load operation - if (!this._isLoaded && this._loadingTask!.Result) - { - // Wait for load to finish - await this._loadingTask; - } - - // Ensure loaded - if (!this._isLoaded) - { - throw new ArgumentNullException($"TurnState hasn't been loaded. Call loadState() first."); - } - - // Find changes and deletions - Record changes = new(); - List deletions = new(); - - foreach (KeyValuePair scope in this._scopes) - { - if (!this._scopes.ContainsKey(scope.Key) || scope.Key == TEMP_SCOPE) - { - continue; - } - TurnStateEntry entry = this._scopes[scope.Key]; - if (entry.StorageKey != null) - { - if (entry.IsDeleted) - { - // Add to deletion list - if (deletions != null) - { - deletions.Add(entry.StorageKey); - } - else - { - deletions = new() { entry.StorageKey }; - } - } - else if (entry.HasChanged) - { - // Add to change set - if (changes == null) - { - changes = new(); - } - changes[entry.StorageKey] = entry.Value!; - } - } - } - - // Do we have a storage provider? - if (storage != null) - { - // Apply changes - List tasks = new(); - if (changes.Keys.Count > 0) - { - tasks.Add(storage.WriteAsync(changes)); - } - - // Apply deletions - if (deletions.Count > 0) - { - tasks.Add(storage.DeleteAsync(deletions.ToArray())); - } - - if (tasks.Count > 0) - { - await Task.WhenAll(tasks.ToArray()); - } - } - } - - /// - /// Computes the state keys - /// - /// Context for the current turn. - /// Stored conversation and user scopes. - protected virtual Dictionary OnComputeStorageKeys(ITurnContext context) - { - // Compute state keys - var activity = context.Activity; - string channelId = activity.ChannelId; - string botId = activity.Recipient.Id; - string conversationId = activity.Conversation.Id; - string userId = activity.From.Id; - - ArgumentNullException.ThrowIfNull(activity, "TurnContext.Activity"); - ArgumentException.ThrowIfNullOrWhiteSpace(channelId, "TurnContext.Activity.ChannelId"); - ArgumentException.ThrowIfNullOrWhiteSpace(botId, "TurnContext.Activity.Recipient.Id"); - ArgumentException.ThrowIfNullOrWhiteSpace(conversationId, "TurnContext.Activity.Conversation.Id"); - ArgumentException.ThrowIfNullOrWhiteSpace(userId, "TurnContext.Activity.From.Id"); - - string conversationKey = $"{channelId}/${botId}/conversations/${conversationId}"; - string userKey = $"{channelId}/${botId}/users/${userId}"; - - Dictionary keys = new(); - keys.Add(CONVERSATION_SCOPE, conversationKey); - keys.Add(USER_SCOPE, userKey); - keys.Add(TEMP_SCOPE, TEMP_SCOPE); - return keys; - } - - private (TurnStateEntry, string) GetScopeAndName(string path) - { - // Get variable scope and name - string[] parts = path.Split('.'); - if (parts.Length > 2) - { - throw new ArgumentException($"Invalid state path: {path}"); - } - else if (parts.Length == 1) - { - parts = parts.Prepend(TEMP_SCOPE).ToArray(); - } - - // Validate scope - TurnStateEntry? scope = GetScope(parts[0]); - if (scope == null) - { - throw new ArgumentNullException($"Invalid state scope: {parts[0]}"); - } - - return (scope, parts[1]); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs deleted file mode 100644 index 650d2cf3..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/State/TurnStateEntry.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Text.Json; - -namespace Microsoft.Agents.BotBuilder.App.State -{ - /// - /// Accessor class for managing an individual state scope. - /// - public class TurnStateEntry - { - private Record _value; - private string _hash; - private static readonly JsonSerializerOptions _serializerOptions = new() { MaxDepth = 64 }; - - /// - /// Constructs the turn state entry. - /// - /// Value to initialize the state scope with. The default is an {} object. - /// Storage key to use when persisting the state scope. - public TurnStateEntry(Record value, string? storageKey = null) - { - ArgumentNullException.ThrowIfNull(value); - _value = value; - StorageKey = storageKey; - _hash = ComputeHash(value); - } - - /// - public bool HasChanged - { - get { return ComputeHash(_value!) != _hash; } - } - - /// - public bool IsDeleted { get; private set; } = false; - - /// - public Record? Value - { - get - { - if (IsDeleted) - { - _value = new(); - IsDeleted = false; - } - - return _value; - } - } - - /// - public string? StorageKey { get; } - - /// - /// Clears the state scope. - /// - public void Delete() - { - IsDeleted = true; - } - - /// - /// Replaces the state scope with a new value. - /// - /// New value to replace the state scope with. - public void Replace(Record value) - { - ArgumentNullException.ThrowIfNull(value); - _value = value; - } - - // TODO: Optimize if possible - /// - /// Computes the hash from the object - /// - /// The object to compute has from - /// Returns a Json object representation - internal static string ComputeHash(object obj) - { - ArgumentNullException.ThrowIfNull(obj); - - return JsonSerializer.Serialize(obj, _serializerOptions); - } - } -} diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs index a0be49df..c36e85b6 100644 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs @@ -51,8 +51,6 @@ public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, ITurn /// public static async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { - await Task.Delay(5000); - // Increment count state. int count = turnState.Conversation.IncrementMessageCount(); From 57fe1ffabbcdbc4a9b960fda95c365f0f35260b3 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 19:33:51 -0600 Subject: [PATCH 28/60] Added back (commented out) InputFiles in Application --- .../App/Application.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 72efd840..b1106f11 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -45,6 +45,7 @@ public Application(ApplicationOptions options) if (Options.TurnStateFactory == null) { + // This defaults to a TurnState with TempState Options.TurnStateFactory = () => new TurnState(); } @@ -625,6 +626,19 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } */ + /* + // Download any input files + IList>? fileDownloaders = this.Options.FileDownloaders; + if (fileDownloaders != null && fileDownloaders.Count > 0) + { + foreach (IInputFileDownloader downloader in fileDownloaders) + { + List files = await downloader.DownloadFilesAsync(turnContext, turnState); + turnState.Temp.InputFiles.AddRange(files); + } + } + */ + bool eventHandlerCalled = false; // TODO: why is this needed? Would not the selector be limiting to "Invoke" anyway, so iterating _routes would be the same thing. @@ -737,5 +751,5 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc return null; } */ - } + } } From 1044e2153bd8fd6c129bab2f40bb52035130a42f Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 21:04:19 -0600 Subject: [PATCH 29/60] Removed IStorage from ApplicationOptions --- .../App/ApplicationBuilder.cs | 11 ----------- .../App/ApplicationOptions.cs | 11 ++++++----- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs index 827a36aa..a9fa18ef 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs @@ -19,17 +19,6 @@ public class ApplicationBuilder /// public ApplicationOptions Options { get; } = new(); - /// - /// Configures the storage system to use for storing the bot's state. - /// - /// The storage system to use. - /// The ApplicationBuilder instance. - public ApplicationBuilder WithStorage(IStorage storage) - { - Options.Storage = storage; - return this; - } - /// /// Configures the turn state factory to use for managing the bot's turn state. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index 30ad816f..7b047913 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -6,6 +6,7 @@ using Microsoft.Agents.Storage; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using System.Collections.Generic; namespace Microsoft.Agents.BotBuilder.App { @@ -14,11 +15,6 @@ namespace Microsoft.Agents.BotBuilder.App /// public class ApplicationOptions { - /// - /// Optional. Storage provider to use for the application. - /// - public IStorage? Storage { get; set; } - /// /// Optional. Options used to customize the processing of Adaptive Card requests. /// @@ -29,6 +25,11 @@ public class ApplicationOptions /// public Func? TurnStateFactory { get; set; } + /// + /// Optional. Array of input file download plugins to use. + /// + public IList? FileDownloaders { get; set; } + /// /// Optional. Logger factory that will be used in this application. /// From 37480b2b7c56523ceb3719feeaadce01adb1ec2d Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 21:48:53 -0600 Subject: [PATCH 30/60] Added ApplicationRouteTests (Core & Teams) --- .../App/ApplicationRouteTests.cs | 1118 ++++++++++++++ .../App/TestUtils/TurnStateConfig.cs | 41 + .../App/ApplicationRouteTests.cs | 1348 +++++++++++++++++ ...osoft.Agents.Extensions.Teams.Tests.csproj | 1 + 4 files changed, 2508 insertions(+) create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs new file mode 100644 index 00000000..ea7f8339 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs @@ -0,0 +1,1118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.BotBuilder.Tests.App +{ + public class ApplicationRouteTests + { + [Fact] + public async Task Test_Application_Route() + { + // Arrange + var activity1 = MessageFactory.Text("hello.1"); + activity1.Recipient = new() { Id = "recipientId" }; + activity1.Conversation = new() { Id = "conversationId" }; + activity1.From = new() { Id = "fromId" }; + activity1.ChannelId = "channelId"; + var activity2 = MessageFactory.Text("hello.2"); + activity2.Recipient = new() { Id = "recipientId" }; + activity2.Conversation = new() { Id = "conversationId" }; + activity2.From = new() { Id = "fromId" }; + activity2.ChannelId = "channelId"; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var messages = new List(); + app.AddRoute( + (context, _) => + Task.FromResult(string.Equals("hello.1", context.Activity.Text)), + (context, _, _) => + { + messages.Add(context.Activity.Text); + return Task.CompletedTask; + }, + false); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(messages); + Assert.Equal("hello.1", messages[0]); + } + + [Fact] + public async Task Test_Application_Routes_Are_Called_InOrder() + { + // Arrange + var activity = MessageFactory.Text("hello.1"); + activity.Recipient = new() { Id = "recipientId" }; + activity.Conversation = new() { Id = "conversationId" }; + activity.From = new() { Id = "fromId" }; + activity.ChannelId = "channelId"; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var selectedRoutes = new List(); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("hello", context.Activity.Text)), + (context, _, _) => + { + selectedRoutes.Add(0); + return Task.CompletedTask; + }, + false); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("hello.1", context.Activity.Text)), + (context, _, _) => + { + selectedRoutes.Add(1); + return Task.CompletedTask; + }, + false); + app.AddRoute( + (_, _) => Task.FromResult(true), + (context, _, _) => + { + selectedRoutes.Add(2); + return Task.CompletedTask; + }, + false); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(selectedRoutes); + Assert.Equal(1, selectedRoutes[0]); + } + + [Fact] + public async Task Test_Application_InvokeRoute() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "invoke.1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "invoke.2", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var turnContext2 = new TurnContext(adapter, activity2); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("invoke.1", context.Activity.Name)), + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }, + true); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(names); + Assert.Equal("invoke.1", names[0]); + } + + [Fact] + public async Task Test_Application_InvokeRoutes_Are_Called_InOrder() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.Invoke, + Name = "invoke.1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var selectedRoutes = new List(); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("invoke", context.Activity.Name)), + (context, _, _) => + { + selectedRoutes.Add(0); + return Task.CompletedTask; + }, + true); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("invoke.1", context.Activity.Name)), + (context, _, _) => + { + selectedRoutes.Add(1); + return Task.CompletedTask; + }, + true); + app.AddRoute( + (_, _) => Task.FromResult(true), + (context, _, _) => + { + selectedRoutes.Add(2); + return Task.CompletedTask; + }, + true); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(selectedRoutes); + Assert.Equal(1, selectedRoutes[0]); + } + + [Fact] + public async Task Test_Application_InvokeRoutes_Are_Called_First() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.Invoke, + Name = "invoke.1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var selectedRoutes = new List(); + app.AddRoute( + (_, _) => Task.FromResult(true), + (context, _, _) => + { + selectedRoutes.Add(0); + return Task.CompletedTask; + }, + true); + app.AddRoute( + (_, _) => Task.FromResult(true), + (context, _, _) => + { + selectedRoutes.Add(1); + return Task.CompletedTask; + }, + false); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(selectedRoutes); + Assert.Equal(0, selectedRoutes[0]); + } + + [Fact] + public async Task Test_Application_No_InvokeRoute_Matched_Fallback_To_Routes() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.Invoke, + Name = "invoke.1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var selectedRoutes = new List(); + app.AddRoute( + (_, _) => Task.FromResult(false), + (context, _, _) => + { + selectedRoutes.Add(0); + return Task.CompletedTask; + }, + true); + app.AddRoute( + (context, _) => Task.FromResult(string.Equals("invoke.1", context.Activity.Name)), + (context, _, _) => + { + selectedRoutes.Add(1); + return Task.CompletedTask; + }, + false); + app.AddRoute( + (_, _) => Task.FromResult(true), + (context, _, _) => + { + selectedRoutes.Add(2); + return Task.CompletedTask; + }, + false); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(selectedRoutes); + Assert.Equal(1, selectedRoutes[0]); + } + + [Fact] + public async Task Test_OnActivity_String_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var types = new List(); + app.OnActivity(ActivityTypes.Message, (context, _, _) => + { + types.Add(context.Activity.Type); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(types); + Assert.Equal(ActivityTypes.Message, types[0]); + } + + [Fact] + public async Task Test_OnActivity_Regex_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageDelete, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var types = new List(); + app.OnActivity(new Regex("^message$"), (context, _, _) => + { + types.Add(context.Activity.Type); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(types); + Assert.Equal(ActivityTypes.Message, types[0]); + } + + [Fact] + public async Task Test_OnActivity_Function_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Name = "Message", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var types = new List(); + app.OnActivity((context, _) => Task.FromResult(context.Activity?.Name != null), (context, _, _) => + { + types.Add(context.Activity.Type); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(types); + Assert.Equal(ActivityTypes.Message, types[0]); + } + + [Fact] + public async Task Test_OnActivity_Multiple_Selectors() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageDelete, + Name = "Delete", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var types = new List(); + app.OnActivity(new MultipleRouteSelector + { + Strings = new[] { ActivityTypes.Invoke }, + Regexes = new[] { new Regex("^message$") }, + RouteSelectors = new RouteSelectorAsync[] { (context, _) => Task.FromResult(context.Activity?.Name != null) }, + }, + (context, _, _) => + { + types.Add(context.Activity.Type); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Equal(3, types.Count); + Assert.Equal(ActivityTypes.Message, types[0]); + Assert.Equal(ActivityTypes.MessageDelete, types[1]); + Assert.Equal(ActivityTypes.Invoke, types[2]); + } + + [Fact] + public async Task Test_OnConversationUpdate_MembersAdded() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + MembersAdded = new List { new() }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_MembersRemoved() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + MembersRemoved = new List { new() }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity2 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(ConversationUpdateEvents.MembersRemoved, (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_UnknownEventName() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate("unknown", + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnMessage_String_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Text = "hello a", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Message, + Text = "welcome", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Text = "hello b", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var texts = new List(); + app.OnMessage("hello", (context, _, _) => + { + texts.Add(context.Activity.Text); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(texts); + Assert.Equal("hello a", texts[0]); + } + + [Fact] + public async Task Test_OnMessage_Regex_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Text = "hello", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Message, + Text = "welcome", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Text = "hello", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var texts = new List(); + app.OnMessage(new Regex("llo"), (context, _, _) => + { + texts.Add(context.Activity.Text); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(texts); + Assert.Equal("hello", texts[0]); + } + + [Fact] + public async Task Test_OnMessage_Function_Selector() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Text = "hello", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var texts = new List(); + app.OnMessage((context, _) => Task.FromResult(context.Activity?.Text != null), (context, _, _) => + { + texts.Add(context.Activity.Text); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Single(texts); + Assert.Equal("hello", texts[0]); + } + + [Fact] + public async Task Test_OnMessage_Multiple_Selectors() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.Message, + Text = "hello a", + Name = "hello", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Message, + Text = "welcome", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + Text = "hello world", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var texts = new List(); + app.OnMessage(new MultipleRouteSelector + { + Strings = new[] { "world" }, + Regexes = new[] { new Regex("come") }, + RouteSelectors = new RouteSelectorAsync[] { (context, _) => Task.FromResult(context.Activity?.Name != null) }, + }, + (context, _, _) => + { + texts.Add(context.Activity.Text); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Equal(3, texts.Count); + Assert.Equal("hello a", texts[0]); + Assert.Equal("welcome", texts[1]); + Assert.Equal("hello world", texts[2]); + } + + [Fact] + public async Task Test_OnMessageReactionsAdded() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.MessageReaction, + ReactionsAdded = new List { new() }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageReaction, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageReactionsAdded((context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnMessageReactionsRemoved() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.MessageReaction, + ReactionsRemoved = new List { new() }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageReaction, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageReactionsRemoved((context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnHandoff() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "handoff/action", + Value = new { Continuation = "test" }, + Id = "test", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Event, + Name = "actionableMessage/executeAction", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/queryLink", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var expectedInvokeResponse = new InvokeResponse + { + Status = 200 + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.OnHandoff((turnContext, _, _, _) => + { + ids.Add(turnContext.Activity.Id); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(ids); + Assert.Equal("test", ids[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs new file mode 100644 index 00000000..64989a2f --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs @@ -0,0 +1,41 @@ +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Storage; +using System; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils +{ + public static class TurnStateConfig + { + public static async Task GetTurnStateWithConversationStateAsync(TurnContext turnContext) + { + if (turnContext == null) + { + throw new ArgumentNullException(nameof(turnContext)); + } + + // Arrange + var state = new TurnState(new MemoryStorage()); + IActivity activity = turnContext.Activity; + string channelId = activity.ChannelId; + string botId = activity.Recipient.Id; + string conversationId = activity.Conversation.Id; + string userId = activity.From.Id; + + await state.LoadStateAsync(turnContext); + + return state; + } + public static TurnContext CreateConfiguredTurnContext() + { + return new TurnContext(new NotImplementedAdapter(), new Activity( + text: "hello", + channelId: "channelId", + recipient: new() { Id = "recipientId" }, + conversation: new() { Id = "conversationId" }, + from: new() { Id = "fromId" } + )); + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs new file mode 100644 index 00000000..23a55482 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs @@ -0,0 +1,1348 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Extensions.Teams.Models; +using Microsoft.Agents.Extensions.Teams.Tests.Model; +using Moq; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.Extensions.Teams.Tests.App +{ + public class ApplicationRouteTests + { + [Fact] + public async Task Test_OnConversationUpdate_ChannelCreated() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelCreated", + Channel = new ChannelInfo(), + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.ChannelCreated, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_ChannelRenamed() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelRenamed", + Channel = new ChannelInfo(), + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.ChannelRenamed, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_ChannelDeleted() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelDeleted", + Channel = new ChannelInfo(), + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.ChannelDeleted, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + + [Fact] + public async Task Test_OnConversationUpdate_ChannelRestored() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelRestored", + Channel = new ChannelInfo(), + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.ChannelRestored, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamRenamed() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamRenamed", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamRenamed, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamDeleted() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamDeleted", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamDeleted, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamHardDeleted() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamHardDeleted", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamHardDeleted, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamArchived() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamArchived", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamArchived, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamUnarchived() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamUnarchived", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamUnarchived, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_TeamRestored() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamRestored", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamRestored, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_SingleEvent() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamRenamed", + Team = new TeamInfo(), + }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + + }; + var activity2 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + ChannelData = new TeamsChannelData + { + EventType = "teamRenamed" + }, + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate(TeamsConversationUpdateEvents.TeamRenamed, (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConversationUpdate_MultipleEvents() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + MembersAdded = new List { new() }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelDeleted", + Channel = new ChannelInfo(), + Team = new TeamInfo(), + }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + Name = "2", + ChannelId = Channels.Msteams, + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + ChannelData = new TeamsChannelData + { + EventType = "teamRenamed" + }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = Channels.Msteams, + }; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new Application(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate( + new[] { TeamsConversationUpdateEvents.TeamRenamed, TeamsConversationUpdateEvents.ChannelDeleted, ConversationUpdateEvents.MembersAdded }, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Equal(2, names.Count); + Assert.Equal("1", names[0]); + Assert.Equal("2", names[1]); + } + + [Fact] + public async Task Test_OnConversationUpdate_BypassNonTeamsEvent() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + MembersAdded = new List { new() }, + Name = "1", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "channelDeleted" + }, + Name = "2", + ChannelId = Channels.Directline, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity3 = new Activity + { + Type = ActivityTypes.ConversationUpdate, + ChannelData = new TeamsChannelData + { + EventType = "teamRenamed" + }, + ChannelId = Channels.Directline, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConversationUpdate( + new[] { TeamsConversationUpdateEvents.TeamRenamed, TeamsConversationUpdateEvents.ChannelDeleted, ConversationUpdateEvents.MembersAdded }, + (context, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnMessageEdit() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.MessageUpdate, + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + EventType = "editMessage" + }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageUpdate, + ChannelId = Channels.Msteams, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelData = new TeamsChannelData + { + EventType = "softDeleteMessage" + } + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + ChannelId = Channels.Msteams, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageEdit((turnContext, _, _) => + { + names.Add(turnContext.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnMessageUnDelete() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.MessageUpdate, + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + EventType = "undeleteMessage" + }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageUpdate, + ChannelId = Channels.Msteams, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelData = new TeamsChannelData + { + EventType = "softDeleteMessage" + } + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + ChannelId = Channels.Msteams, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageUndelete((turnContext, _, _) => + { + names.Add(turnContext.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnMessageDelete() + { + // Arrange + var activity1 = new Activity + { + Type = ActivityTypes.MessageDelete, + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + EventType = "softDeleteMessage" + }, + Name = "1", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.MessageDelete, + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + EventType = "unknown" + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity3 = new Activity + { + Type = ActivityTypes.Message, + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnMessageDelete((turnContext, _, _) => + { + names.Add(turnContext.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(names); + Assert.Equal("1", names[0]); + } + + [Fact] + public async Task Test_OnConfigFetch() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/fetch", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/fetch", + ChannelId = Channels.Outlook, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/submit", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity4 = new Activity + { + Type = ActivityTypes.Message, + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnContext4 = new TurnContext(adapter, activity4); + var configResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = configResponseMock.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConfigFetch((turnContext, _, _, _) => + { + names.Add(turnContext.Activity.Name); + return Task.FromResult(configResponseMock.Object); + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext4); + + // Assert + Assert.Single(names); + Assert.Equal("config/fetch", names[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnConfigSubmit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + object data = new + { + testKey = "testValue" + }; + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/submit", + ChannelId = Channels.Msteams, + Value = JObject.FromObject(data), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/submit", + ChannelId = Channels.Outlook, + Value = JObject.FromObject(data), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "config/fetch", + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var activity4 = new Activity + { + Type = ActivityTypes.Message, + ChannelId = Channels.Msteams, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnContext4 = new TurnContext(adapter, activity4); + var configResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = configResponseMock.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnConfigSubmit((turnContext, _, configData, _) => + { + Assert.NotNull(configData); + Assert.Equal(configData, configData as JObject); + names.Add(turnContext.Activity.Name); + return Task.FromResult(configResponseMock.Object); + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext4); + + // Assert + Assert.Single(names); + Assert.Equal("config/submit", names[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnFileConsentAccept() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "fileConsent/invoke", + Value = new + { + action = "accept" + }, + Id = "test", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "fileConsent/invoke", + Value = new + { + action = "decline" + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/queryLink", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var expectedInvokeResponse = new InvokeResponse + { + Status = 200 + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.OnFileConsentAccept((turnContext, _, _, _) => + { + ids.Add(turnContext.Activity.Id); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(ids); + Assert.Equal("test", ids[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnFileConsentDecline() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "fileConsent/invoke", + Value = new + { + action = "decline" + }, + Id = "test", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "fileConsent/invoke", + Value = new + { + action = "accept" + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/queryLink", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var expectedInvokeResponse = new InvokeResponse + { + Status = 200 + }; + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.OnFileConsentDecline((turnContext, _, _, _) => + { + ids.Add(turnContext.Activity.Id); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(ids); + Assert.Equal("test", ids[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnO365ConnectorCardAction() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity1 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "actionableMessage/executeAction", + Value = new { }, + Id = "test", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity2 = new Activity + { + Type = ActivityTypes.Event, + Name = "actionableMessage/executeAction", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var activity3 = new Activity + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/queryLink", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }; + var turnContext1 = new TurnContext(adapter, activity1); + var turnContext2 = new TurnContext(adapter, activity2); + var turnContext3 = new TurnContext(adapter, activity3); + var expectedInvokeResponse = new InvokeResponse + { + Status = 200 + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.OnO365ConnectorCardAction((turnContext, _, _, _) => + { + ids.Add(turnContext.Activity.Id); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext3); + + // Assert + Assert.Single(ids); + Assert.Equal("test", ids[0]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnTeamsReadReceipt() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.Event, + ChannelId = Channels.Msteams, + Name = "application/vnd.microsoft.readReceipt", + Value = new + { + lastReadMessageId = "10101010", + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnTeamsReadReceipt((context, _, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Single(names); + Assert.Equal("application/vnd.microsoft.readReceipt", names[0]); + } + + [Fact] + public async Task Test_OnTeamsReadReceipt_IncorrectName() + { + // Arrange + var activity = new Activity + { + Type = ActivityTypes.Event, + ChannelId = Channels.Msteams, + Name = "application/vnd.microsoft.meetingStart", + Value = JObject.FromObject(new + { + lastReadMessageId = "10101010", + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }; + var adapter = new NotImplementedAdapter(); + var turnContext = new TurnContext(adapter, activity); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var names = new List(); + app.OnTeamsReadReceipt((context, _, _, _) => + { + names.Add(context.Activity.Name); + return Task.CompletedTask; + }); + + // Act + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Empty(names); + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj index 2a3863a3..9b2e70b0 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Microsoft.Agents.Extensions.Teams.Tests.csproj @@ -31,5 +31,6 @@ + From 0df3f6675ab0207281bae837eda94f05586cbb2a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 23:14:54 -0600 Subject: [PATCH 31/60] Added MeetingsTests and MessageExtensionsTests --- .../Serialization/ProtocolJsonSerializer.cs | 10 +- .../App/Meetings/MeetingsFeature.cs | 8 +- .../MessageExtensionsFeature.cs | 22 +- .../App/TestUtils/TurnStateConfig.cs | 5 + .../App/ApplicationRouteTests.cs | 10 +- .../App/MeetingsTests.cs | 187 +++ .../App/MessageExtensionsTests.cs | 1324 +++++++++++++++++ .../MessagingExtensionActionResponseTests.cs | 3 - 8 files changed, 1544 insertions(+), 25 deletions(-) create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs index e141e2ca..52ad3e10 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs @@ -121,13 +121,19 @@ public static void Add(this IDictionary target, object valu /// Convert an object to the desired type using serialization and deserialization. /// /// The object to be converted to desired type: string, MemoryStream, object + /// /// The type of object to convert to. /// The converted object. - public static T ToObject(object value) + public static T ToObject(object value, Func defaultFactory = null) { if (value == null) { - throw new ArgumentNullException(nameof(value)); + if (defaultFactory != null) + { + return defaultFactory(); + } + + return default; } if (value is T result) diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs index 5175c6e9..f2a044a4 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs @@ -42,7 +42,7 @@ public Application OnStart(MeetingStartHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingStartEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + MeetingStartEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -65,7 +65,7 @@ public Application OnEnd(MeetingEndHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingEndEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + MeetingEndEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -88,7 +88,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); @@ -111,7 +111,7 @@ public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { - MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); + MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index 75e003e4..1b670ff7 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -10,6 +10,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -772,21 +773,20 @@ private static RouteSelectorAsync CreateTaskSelector(Func isMatch, return Task.FromResult(false); } - // TODO - /* - JObject? obj = turnContext.Activity.Value as JObject; - if (obj == null) + if (turnContext.Activity.Value == null) { return Task.FromResult(false); } - bool isCommandMatch = obj.TryGetValue("commandId", out JToken? commandId) && commandId != null && commandId.Type == JTokenType.String && isMatch(commandId.Value()!); - JToken? previewActionToken = obj.GetValue("botMessagePreviewAction"); - bool isPreviewActionMatch = string.IsNullOrEmpty(botMessagePreviewAction) - ? previewActionToken == null || string.IsNullOrEmpty(previewActionToken.Value()) - : previewActionToken != null && string.Equals(botMessagePreviewAction, previewActionToken.Value()); + + var obj = ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value); + + bool isCommandMatch = obj.TryGetValue("commandId", out JsonElement commandId) && commandId.ValueKind == JsonValueKind.String && isMatch(commandId.ToString()); + + bool isPreviewActionMatch = !obj.TryGetValue("botMessagePreviewAction", out JsonElement previewActionToken) + || string.IsNullOrEmpty(previewActionToken.ToString()) + || string.Equals(botMessagePreviewAction, previewActionToken.ToString()); + return Task.FromResult(isCommandMatch && isPreviewActionMatch); - */ - return Task.FromResult(false); }; return routeSelector; } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs index 64989a2f..1cd9520e 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs @@ -2,6 +2,8 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils @@ -15,6 +17,9 @@ public static async Task GetTurnStateWithConversationStateAsync(Turn throw new ArgumentNullException(nameof(turnContext)); } + Dictionary dictionary = new Dictionary(); + + // Arrange var state = new TurnState(new MemoryStorage()); IActivity activity = turnContext.Activity; diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs index 23a55482..6ee9ce2c 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs @@ -6,11 +6,11 @@ using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.App; using Microsoft.Agents.Extensions.Teams.Models; using Microsoft.Agents.Extensions.Teams.Tests.Model; using Moq; -using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -955,7 +955,7 @@ void CaptureSend(IActivity[] arg) Type = ActivityTypes.Invoke, Name = "config/submit", ChannelId = Channels.Msteams, - Value = JObject.FromObject(data), + Value = ProtocolJsonSerializer.ToJsonElements(data), Recipient = new() { Id = "recipientId" }, Conversation = new() { Id = "conversationId" }, From = new() { Id = "fromId" }, @@ -965,7 +965,7 @@ void CaptureSend(IActivity[] arg) Type = ActivityTypes.Invoke, Name = "config/submit", ChannelId = Channels.Outlook, - Value = JObject.FromObject(data), + Value = ProtocolJsonSerializer.ToJsonElements(data), Recipient = new() { Id = "recipientId" }, Conversation = new() { Id = "conversationId" }, From = new() { Id = "fromId" }, @@ -1008,7 +1008,7 @@ void CaptureSend(IActivity[] arg) app.OnConfigSubmit((turnContext, _, configData, _) => { Assert.NotNull(configData); - Assert.Equal(configData, configData as JObject); + //Assert.Equal(configData, configData as JObject); names.Add(turnContext.Activity.Name); return Task.FromResult(configResponseMock.Object); }); @@ -1314,7 +1314,7 @@ public async Task Test_OnTeamsReadReceipt_IncorrectName() Type = ActivityTypes.Event, ChannelId = Channels.Msteams, Name = "application/vnd.microsoft.meetingStart", - Value = JObject.FromObject(new + Value = ProtocolJsonSerializer.ToJsonElements(new { lastReadMessageId = "10101010", }), diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs new file mode 100644 index 00000000..1dc91c8a --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs @@ -0,0 +1,187 @@ + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Extensions.Teams.Tests.Model; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.Extensions.Teams.Tests.App +{ + public class MeetingsTests + { + [Fact] + public async Task Test_OnStart() + { + // Arrange + var adapter = new NotImplementedAdapter(); + var turnContexts = CreateMeetingTurnContext("application/vnd.microsoft.meetingStart", adapter); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContexts[0]); + + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.Meetings.OnStart((context, _, _, _) => + { + ids.Add(context.Activity.Id); + return Task.CompletedTask; + }); + + // Act + foreach (var turnContext in turnContexts) + { + await app.OnTurnAsync(turnContext); + } + + // Assert + Assert.Single(ids); + Assert.Equal("test.id", ids[0]); + } + + [Fact] + public async Task Test_OnEnd() + { + // Arrange + var adapter = new NotImplementedAdapter(); + var turnContexts = CreateMeetingTurnContext("application/vnd.microsoft.meetingEnd", adapter); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContexts[0]); + + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.Meetings.OnEnd((context, _, _, _) => + { + ids.Add(context.Activity.Id); + return Task.CompletedTask; + }); + + // Act + foreach (var turnContext in turnContexts) + { + await app.OnTurnAsync(turnContext); + } + + // Assert + Assert.Single(ids); + Assert.Equal("test.id", ids[0]); + } + + [Fact] + public async Task Test_OnParticipantsJoin() + { + // Arrange + var adapter = new NotImplementedAdapter(); + var turnContexts = CreateMeetingTurnContext("application/vnd.microsoft.meetingParticipantJoin", adapter); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContexts[0]); + + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.Meetings.OnParticipantsJoin((context, _, _, _) => + { + ids.Add(context.Activity.Id); + return Task.CompletedTask; + }); + + // Act + foreach (var turnContext in turnContexts) + { + await app.OnTurnAsync(turnContext); + } + + // Assert + Assert.Single(ids); + Assert.Equal("test.id", ids[0]); + } + + [Fact] + public async Task Test_OnParticipantsLeave() + { + // Arrange + var adapter = new NotImplementedAdapter(); + var turnContexts = CreateMeetingTurnContext("application/vnd.microsoft.meetingParticipantLeave", adapter); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContexts[0]); + + var app = new TeamsApplication(new() + { + RemoveRecipientMention = false, + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var ids = new List(); + app.Meetings.OnParticipantsLeave((context, _, _, _) => + { + ids.Add(context.Activity.Id); + return Task.CompletedTask; + }); + + // Act + foreach (var turnContext in turnContexts) + { + await app.OnTurnAsync(turnContext); + } + + // Assert + Assert.Single(ids); + Assert.Equal("test.id", ids[0]); + } + + private static TurnContext[] CreateMeetingTurnContext(string activityName, ChannelAdapter adapter) + { + return new TurnContext[] + { + new(adapter, new Activity + { + Type = ActivityTypes.Event, + ChannelId = Channels.Msteams, + Name = activityName, + Id = "test.id", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }), + new(adapter, new Activity + { + Type = ActivityTypes.Event, + ChannelId = Channels.Msteams, + Name = "fake.name", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }), + new(adapter, new Activity + { + Type = ActivityTypes.Invoke, + ChannelId = Channels.Msteams, + Name = activityName, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }), + new(adapter, new Activity + { + Type = ActivityTypes.Event, + ChannelId = Channels.Webchat, + Name = activityName, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + }), + }; + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs new file mode 100644 index 00000000..45bbb4a1 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs @@ -0,0 +1,1324 @@ +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; +using Microsoft.Agents.Extensions.Teams.Models; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.Extensions.Teams.Tests.App +{ + public class MessageExtensionsTests + { + [Fact] + public async Task Test_OnSubmitAction_CommandId() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + data = new + { + title = "test-title", + content = "test-content" + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var actionResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = actionResponseMock.Object + }; + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SubmitActionHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + MessageExtensionActionData actionData = Cast(data); + Assert.Equal("test-title", actionData.Title); + Assert.Equal("test-content", actionData.Content); + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnSubmitAction("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnSubmitAction_CommandId_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + data = new + { + title = "test-title", + content = "test-content" + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var actionResponseMock = new Mock(); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SubmitActionHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + MessageExtensionActionData actionData = Cast(data); + Assert.Equal("test-title", actionData.Title); + Assert.Equal("test-content", actionData.Content); + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnSubmitAction("not-test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnSubmitAction_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/fetchTask", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var actionResponseMock = new Mock(); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + SubmitActionHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnSubmitAction(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnBotMessagePreviewEdit_CommandId() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity = new Activity() + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + botMessagePreviewAction = "edit", + botActivityPreview = new List { activity } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var actionResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = actionResponseMock.Object, + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + + }); + BotMessagePreviewEditHandlerAsync handler = (turnContext, turnState, activityPreview, cancellationToken) => + { + Assert.Equivalent(activity, activityPreview); + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewEdit("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnBotMessagePreviewEdit_CommandId_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity = new Activity() + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + botMessagePreviewAction = "send", + botActivityPreview = new List { activity } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var actionResponseMock = new Mock(); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + BotMessagePreviewEditHandlerAsync handler = (turnContext, turnState, activityPreview, cancellationToken) => + { + Assert.Equivalent(activity, activityPreview); + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewEdit("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnBotMessagePreviewEdit_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/fetchTask", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var actionResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + BotMessagePreviewEditHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(actionResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewEdit(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnBotMessagePreviewSend_CommandId() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity = new Activity() + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + botMessagePreviewAction = "send", + botActivityPreview = new List { activity } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + BotMessagePreviewSendHandler handler = (turnContext, turnState, activityPreview, cancellationToken) => + { + Assert.Equivalent(activity, activityPreview); + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewSend("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnBotMessagePreviewSend_CommandId_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var activity = new Activity() + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }; + + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + botMessagePreviewAction = "edit", + botActivityPreview = new List { activity } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + BotMessagePreviewSendHandler handler = (turnContext, turnState, activityPreview, cancellationToken) => + { + Assert.Equivalent(activity, activityPreview); + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewSend("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnBotMessagePreviewSend_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/fetchTask", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + BotMessagePreviewSendHandler handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnBotMessagePreviewSend(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnFetchTask_CommandId() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/fetchTask", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = taskModuleResponseMock.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + FetchTaskHandlerAsync handler = (turnContext, turnState, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnFetchTask("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnFetchTask_CommandId_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/fetchTask", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + FetchTaskHandlerAsync handler = (turnContext, turnState, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnFetchTask("not-test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnFetchTask_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/submitAction", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + FetchTaskHandlerAsync handler = (turnContext, turnState, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.MessageExtensions.OnFetchTask(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected MessageExtensions.OnFetchTask() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnQuery_CommandId() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + parameters = new List + { + new("test-name", "test-value") + }, + queryOptions = new + { + count = 10, + skip = 0 + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => + { + Assert.Single(query.Parameters); + Assert.Equal("test-value", query.Parameters["test-name"].ToString()); + Assert.Equal(10, query.Count); + Assert.Equal(0, query.Skip); + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQuery("test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnQuery_CommandId_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + commandId = "test-command", + parameters = new List + { + new("test-name", "test-value") + }, + queryOptions = new + { + count = 10, + skip = 0 + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => + { + Assert.Single(query.Parameters); + Assert.Equal("test-value", query.Parameters["test-name"]); + Assert.Equal(10, query.Count); + Assert.Equal(0, query.Skip); + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQuery("not-test-command", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnQuery_RouteSelector_NotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/selectItem", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + QueryHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQuery(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected MessageExtensions.OnQuery() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_SelectItem() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/selectItem", + Value = ProtocolJsonSerializer.ToJsonElements(new { }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SelectItemHandlerAsync handler = (turnContext, turnState, item, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnSelectItem(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_SelectItem_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + Value = ProtocolJsonSerializer.ToJsonElements(new { }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SelectItemHandlerAsync handler = (turnContext, turnState, item, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnSelectItem(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnQueryLink() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/queryLink", + Value = new + { + url = "test-url" + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryLinkHandlerAsync handler = (turnContext, turnState, url, cancellationToken) => + { + Assert.Equal("test-url", url); + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQueryLink(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnQueryLink_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryLinkHandlerAsync handler = (turnContext, turnState, url, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQueryLink(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnAnonymousQueryLink() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/anonymousQueryLink", + Value = new + { + url = "test-url" + }, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryLinkHandlerAsync handler = (turnContext, turnState, url, cancellationToken) => + { + Assert.Equal("test-url", url); + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnAnonymousQueryLink(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnAnonymousQueryLink_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/query", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + QueryLinkHandlerAsync handler = (turnContext, turnState, url, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnAnonymousQueryLink(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnQueryUrlSetting() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/querySettingUrl", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new MessagingExtensionActionResponse() + { + ComposeExtension = messagingExtensionResultMock.Object + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + QueryUrlSettingHandlerAsync handler = (turnContext, turnState, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnQueryUrlSetting(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnQueryUrlSetting_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/settings", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var messagingExtensionResultMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + QueryLinkHandlerAsync handler = (turnContext, turnState, url, cancellationToken) => + { + return Task.FromResult(messagingExtensionResultMock.Object); + }; + + // Act + app.MessageExtensions.OnAnonymousQueryLink(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnConfigureSettings() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/setting", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + state = "test-state" + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200 + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + ConfigureSettingsHandler handler = (turnContext, turnState, settings, cancellationToken) => + { + var obj = ProtocolJsonSerializer.ToJsonElements(settings); + Assert.NotNull(obj); + Assert.Equal("test-state", obj["state"].ToString()); + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnConfigureSettings(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnConfigureSettings_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/querySettingUrl", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + ConfigureSettingsHandler handler = (turnContext, turnState, settings, cancellationToken) => + { + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnConfigureSettings(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnCardButtonClicked() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/onCardButtonClicked", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200 + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + CardButtonClickedHandler handler = (turnContext, turnState, cardData, cancellationToken) => + { + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnCardButtonClicked(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnCardButtonClicked_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "composeExtension/querySettingUrl", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + TurnStateFactory = () => turnState.Result, + }); + CardButtonClickedHandler handler = (turnContext, turnState, cardData, cancellationToken) => + { + return Task.CompletedTask; + }; + + // Act + app.MessageExtensions.OnCardButtonClicked(handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + private static T Cast(object data) + { + Assert.NotNull(data); + T result = ProtocolJsonSerializer.ToObject(data); + Assert.NotNull(result); + return result; + } + + private sealed class MessageExtensionActionData + { + public string Title { get; set; } + + public string Content { get; set; } + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs index 23cdcad8..adbeeb96 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionResponseTests.cs @@ -4,10 +4,7 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; -using Newtonsoft.Json.Linq; -using System; using Xunit; -using static System.Net.Mime.MediaTypeNames; namespace Microsoft.Agents.Extensions.Teams.Tests.Model { From 369022c04ca0c12a94a44709558993782201c710 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 10 Feb 2025 23:57:43 -0600 Subject: [PATCH 32/60] Added TaskModulesTests --- .../App/TaskModules/TaskModulesFeature.cs | 53 +-- .../App/TeamsApplication.cs | 6 +- .../App/TeamsApplicationOptions.cs | 16 + .../App/MeetingsTests.cs | 4 +- .../App/MessageExtensionsTests.cs | 5 +- .../App/TaskModulesTests.cs | 313 ++++++++++++++++++ 6 files changed, 359 insertions(+), 38 deletions(-) create mode 100644 src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs index 188572c4..8e62c462 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs @@ -8,6 +8,8 @@ using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; using System; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -22,16 +24,15 @@ public class TaskModulesFeature private static readonly string FETCH_INVOKE_NAME = "task/fetch"; private static readonly string SUBMIT_INVOKE_NAME = "task/submit"; - //TODO - //private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; + private static readonly string DEFAULT_TASK_DATA_FILTER = "verb"; - private readonly Application _app; + private readonly TeamsApplication _app; /// /// Creates a new instance of the TaskModules class. /// /// The top level application class to register handlers with. - public TaskModulesFeature(Application app) + public TaskModulesFeature(TeamsApplication app) { this._app = app; } @@ -44,16 +45,12 @@ public TaskModulesFeature(Application app) /// The application instance for chaining purposes. public Application OnFetch(string verb, FetchHandlerAsync handler) { - throw new NotImplementedException(); - - //TODO - /* ArgumentNullException.ThrowIfNull(verb); ArgumentNullException.ThrowIfNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); - */ } /// @@ -64,16 +61,12 @@ public Application OnFetch(string verb, FetchHandlerAsync handler) /// The application instance for chaining purposes. public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) { - throw new NotImplementedException(); - - //TODO - /* ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, FETCH_INVOKE_NAME); return OnFetch(routeSelector, handler); - */ } /// @@ -154,16 +147,12 @@ public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsy /// The application instance for chaining purposes. public Application OnSubmit(string verb, SubmitHandlerAsync handler) { - throw new NotImplementedException(); - - //TODO - /* ArgumentNullException.ThrowIfNull(verb); ArgumentNullException.ThrowIfNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => string.Equals(verb, input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); - */ } @@ -175,16 +164,12 @@ public Application OnSubmit(string verb, SubmitHandlerAsync handler) /// The application instance for chaining purposes. public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) { - throw new NotImplementedException(); - - //TODO - /* ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); + string filter = _app.Options.TaskModules?.TaskDataFilter ?? DEFAULT_TASK_DATA_FILTER; RouteSelectorAsync routeSelector = CreateTaskSelector((string input) => verbPattern.IsMatch(input), filter, SUBMIT_INVOKE_NAME); return OnSubmit(routeSelector, handler); - */ } /// @@ -202,7 +187,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync TaskModuleAction? taskModuleAction; if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) || !string.Equals(turnContext.Activity.Name, SUBMIT_INVOKE_NAME) - || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity)) == null) + || (taskModuleAction = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value)) == null) { throw new InvalidOperationException($"Unexpected TaskModules.OnSubmit() triggered for activity type: {turnContext.Activity.Type}"); } @@ -268,26 +253,24 @@ private static RouteSelectorAsync CreateTaskSelector(Func isMatch, return Task.FromResult(false); } - //TODO - /* - JObject? obj = turnContext.Activity.Value as JObject; - if (obj == null) + if (turnContext.Activity.Value == null) { return Task.FromResult(false); } - JObject? data = obj["data"] as JObject; - if (data == null) + var obj = ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value); + + if (!obj.ContainsKey("data")) { return Task.FromResult(false); } - bool isVerbMatch = data.TryGetValue(filter, out JToken? filterField) && filterField != null && filterField.Type == JTokenType.String - && isMatch(filterField.Value()!); + var data = JsonObject.Create(obj["data"]); + + bool isVerbMatch = data.TryGetPropertyValue(filter, out JsonNode filterField) && filterField.GetValueKind() == JsonValueKind.String + && isMatch(filterField.ToString()); return Task.FromResult(isVerbMatch); - */ - return Task.FromResult(false); }; return routeSelector; } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs index d0f95889..f3e3621b 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs @@ -34,13 +34,17 @@ public class TeamsApplication : Application /// /// Optional. Options used to configure the application. /// - public TeamsApplication(ApplicationOptions options) : base(options) + public TeamsApplication(TeamsApplicationOptions options) : base(options) { Meetings = new MeetingsFeature(this); MessageExtensions = new MessageExtensionsFeature(this); TaskModules = new TaskModulesFeature(this); + + Options = options; } + public new TeamsApplicationOptions Options { get; } + /// /// Fluent interface for accessing Meetings' specific features. /// diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs new file mode 100644 index 00000000..4fbc1d18 --- /dev/null +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.Extensions.Teams.App.TaskModules; + +namespace Microsoft.Agents.Extensions.Teams.App +{ + public class TeamsApplicationOptions : ApplicationOptions + { + /// + /// Optional. Options used to customize the processing of Task Modules requests. + /// + public TaskModulesOptions? TaskModules { get; set; } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs index 1dc91c8a..035e8124 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; using Microsoft.Agents.Core.Models; diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs index 45bbb4a1..1c3f0cda 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs @@ -1,4 +1,7 @@ -using Microsoft.Agents.BotBuilder; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs new file mode 100644 index 00000000..1923544d --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs @@ -0,0 +1,313 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Extensions.Teams.App.TaskModules; +using Microsoft.Agents.Extensions.Teams.Models; +using Moq; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.Extensions.Teams.Tests.App +{ + public class TaskModulesTests + { + [Fact] + public async Task Test_OnFetch_Verb() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/fetch", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + data = new + { + msteams = new + { + type = "task/fetch", + }, + verb = "test-verb", + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = taskModuleResponseMock.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + FetchHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnFetch("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnFetch_Verb_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/fetch", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + data = new + { + msteams = new + { + type = "task/fetch", + }, + verb = "not-test-verb", + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var taskModuleResponseMock = new Mock(); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + FetchHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnFetch("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnFetch_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/fetch", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + FetchHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnFetch(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected TaskModules.OnFetch() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnSubmit_Verb() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/submit", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + data = new + { + msteams = new + { + type = "task/submit", + }, + verb = "test-verb", + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = taskModuleResponseMock.Object + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SubmitHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnSubmit("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnSubmit_Verb_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/submit", + Value = ProtocolJsonSerializer.ToJsonElements(new + { + data = new + { + msteams = new + { + type = "task/submit", + }, + verb = "not-test-verb", + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + + SubmitHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnSubmit("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnSubmit_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "task/submit", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var taskModuleResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new TeamsApplication(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + SubmitHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(taskModuleResponseMock.Object); + }; + + // Act + app.TaskModules.OnSubmit(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected TaskModules.OnSubmit() triggered for activity type: invoke", exception.Message); + } + } +} From 77d40f67dc2f74b90a7f6d6372aacee75d3fddb7 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 11 Feb 2025 00:02:04 -0600 Subject: [PATCH 33/60] Moved TeamsActivityHandlerTests --- .../{Model => Handler}/TeamsActivityHandlerTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{Model => Handler}/TeamsActivityHandlerTests.cs (99%) diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Handler/TeamsActivityHandlerTests.cs similarity index 99% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Handler/TeamsActivityHandlerTests.cs index 37366f6f..ffb5d2c8 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TeamsActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Handler/TeamsActivityHandlerTests.cs @@ -11,8 +11,9 @@ using System.Globalization; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.Extensions.Teams.Tests.Model; -namespace Microsoft.Agents.Extensions.Teams.Tests.Model +namespace Microsoft.Agents.Extensions.Teams.Tests.Handler { public class TeamsActivityHandlerTests { From 7a16cdd591d181bdd35ac90644320047eed70ce4 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 11 Feb 2025 00:04:30 -0600 Subject: [PATCH 34/60] Moved TestActivityHandler --- .../{ => Handler}/TestActivityHandler.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/tests/Microsoft.Agents.Extensions.Teams.Tests/{ => Handler}/TestActivityHandler.cs (100%) diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/TestActivityHandler.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Handler/TestActivityHandler.cs similarity index 100% rename from src/tests/Microsoft.Agents.Extensions.Teams.Tests/TestActivityHandler.cs rename to src/tests/Microsoft.Agents.Extensions.Teams.Tests/Handler/TestActivityHandler.cs From 7ac9e6aea89106241fed5403aeb5d8e4c119ed97 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 11 Feb 2025 08:09:17 -0600 Subject: [PATCH 35/60] Added AdaptiveCardsTests --- .../App/AdaptiveCards/AdaptiveCardsFeature.cs | 1 - .../App/AdaptiveCardsTests.cs | 489 ++++++++++++++++++ .../App/TaskModulesTests.cs | 9 +- 3 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index b3e7827f..24969d4e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -418,7 +418,6 @@ private static RouteSelectorAsync CreateActionSubmitSelector(Func string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(turnContext.Activity.Text) && turnContext.Activity.Value != null - && (obj = turnContext.Activity.Value as JsonObject) != null && obj[filter] != null && obj[filter]!.GetValueKind() == System.Text.Json.JsonValueKind.String && isMatch(obj[filter]!.ToString()!)); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs new file mode 100644 index 00000000..8c7f2ae0 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs @@ -0,0 +1,489 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Moq; +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.BotBuilder.Tests.App +{ + public class AdaptiveCardsTests + { + [Fact] + public async Task Test_OnActionExecute_Verb() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "adaptiveCard/action", + Value = ProtocolJsonSerializer.ToObject(new + { + action = new + { + type = "Action.Execute", + verb = "test-verb", + data = new { testKey = "test-value" } + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var adaptiveCardInvokeResponseMock = new Mock(); + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = adaptiveCardInvokeResponseMock.Object, + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + ActionExecuteHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + TestAdaptiveCardActionData actionData = Cast(data); + Assert.Equal("test-value", actionData.TestKey); + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); + }; + + // Act + app.AdaptiveCards.OnActionExecute("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnActionExecute_Verb_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext1 = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "adaptiveCard/action", + Value = ProtocolJsonSerializer.ToObject(new + { + action = new + { + type = "Action.Execute", + verb = "not-test-verb" + } + }), + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnContext2 = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "application/search", + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId" + }); + var adaptiveCardInvokeResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + ActionExecuteHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); + }; + + // Act + app.AdaptiveCards.OnActionExecute("test-verb", handler); + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnActionExecute_RouteSelector_ActivityNotMatched() + { + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "application/search", + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var adaptiveCardInvokeResponseMock = new Mock(); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + ActionExecuteHandlerAsync handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); + }; + + // Act + app.AdaptiveCards.OnActionExecute(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: invoke", exception.Message); + } + + [Fact] + public async Task Test_OnActionSubmit_Verb() + { + // Arrange + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Message, + Value = ProtocolJsonSerializer.ToObject(new + { + verb = "test-verb", + testKey = "test-value" + }), + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var called = false; + ActionSubmitHandler handler = (turnContext, turnState, data, cancellationToken) => + { + called = true; + TestAdaptiveCardSubmitData submitData = Cast(data); + Assert.Equal("test-verb", submitData.Verb); + Assert.Equal("test-value", submitData.TestKey); + return Task.CompletedTask; + }; + + // Act + app.AdaptiveCards.OnActionSubmit("test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Test_OnActionSubmit_Verb_NotHit() + { + // Arrange + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Message, + Value = ProtocolJsonSerializer.ToObject(new + { + verb = "test-verb", + testKey = "test-value" + }), + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + var called = false; + ActionSubmitHandler handler = (turnContext, turnState, data, cancellationToken) => + { + called = true; + TestAdaptiveCardSubmitData submitData = Cast(data); + Assert.Equal("test-verb", submitData.Verb); + Assert.Equal("test-value", submitData.TestKey); + return Task.CompletedTask; + }; + + // Act + app.AdaptiveCards.OnActionSubmit("not-test-verb", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.False(called); + } + + [Fact] + public async Task Test_OnActionSubmit_RouteSelector_ActivityNotMatched() + { + // Arrange + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Message, + Text = "test-text", + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + ActionSubmitHandler handler = (turnContext, turnState, data, cancellationToken) => + { + return Task.CompletedTask; + }; + + // Act + app.AdaptiveCards.OnActionSubmit(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected AdaptiveCards.OnActionSubmit() triggered for activity type: message", exception.Message); + } + + [Fact] + public async Task Test_OnSearch_Dataset() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "application/search", + Value = ProtocolJsonSerializer.ToObject(new + { + kind = "search", + queryText = "test-query", + queryOptions = new + { + skip = 0, + top = 15 + }, + dataset = "test-dataset" + }), + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + IList searchResults = new List + { + new("test-title", "test-value") + }; + var expectedInvokeResponse = new InvokeResponse() + { + Status = 200, + Body = new SearchInvokeResponse() + { + StatusCode = 200, + Type = "application/vnd.microsoft.search.searchResponse", + Value = new + { + Results = searchResults + } + } + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SearchHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => + { + Assert.Equal("test-query", query.Parameters.QueryText); + Assert.Equal("test-dataset", query.Parameters.Dataset); + return Task.FromResult(searchResults); + }; + + // Act + app.AdaptiveCards.OnSearch("test-dataset", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("invokeResponse", activitiesToSend[0].Type); + Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); + } + + [Fact] + public async Task Test_OnSearch_Dataset_NotHit() + { + // Arrange + IActivity[] activitiesToSend = null; + void CaptureSend(IActivity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "application/search", + Value = ProtocolJsonSerializer.ToObject(new + { + kind = "search", + queryText = "test-query", + queryOptions = new + { + skip = 0, + top = 15 + }, + dataset = "test-dataset" + }), + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + IList searchResults = new List + { + new("test-title", "test-value") + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + SearchHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => + { + Assert.Equal("test-query", query.Parameters.QueryText); + Assert.Equal("test-dataset", query.Parameters.Dataset); + return Task.FromResult(searchResults); + }; + + // Act + app.AdaptiveCards.OnSearch("not-test-dataset", handler); + await app.OnTurnAsync(turnContext); + + // Assert + Assert.Null(activitiesToSend); + } + + [Fact] + public async Task Test_OnSearch_RouteSelector_ActivityNotMatched() + { + // Arrange + var adapter = new SimpleAdapter(); + var turnContext = new TurnContext(adapter, new Activity() + { + Type = ActivityTypes.Invoke, + Name = "adaptiveCard/action", + Recipient = new("test-id"), + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + IList searchResults = new List + { + new("test-title", "test-value") + }; + var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + var app = new Application(new() + { + StartTypingTimer = false, + TurnStateFactory = () => turnState.Result, + }); + RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => + { + return Task.FromResult(true); + }; + SearchHandlerAsync handler = (turnContext, turnState, query, cancellationToken) => + { + Assert.Equal("test-query", query.Parameters.QueryText); + Assert.Equal("test-dataset", query.Parameters.Dataset); + return Task.FromResult(searchResults); + }; + + // Act + app.AdaptiveCards.OnSearch(routeSelector, handler); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + + // Assert + Assert.Equal("Unexpected AdaptiveCards.OnSearch() triggered for activity type: invoke", exception.Message); + } + + private static T Cast(object data) + { + T result = ProtocolJsonSerializer.ToObject(data); + Assert.NotNull(result); + return result; + } + + private sealed class TestAdaptiveCardActionData + { + public string TestKey { get; set; } + } + + private sealed class TestAdaptiveCardSubmitData + { + public string Verb { get; set; } + + public string TestKey { get; set; } + } + } +} diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs index 1923544d..c35944bb 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs @@ -12,6 +12,7 @@ using Microsoft.Agents.Extensions.Teams.Models; using Moq; using System; +using System.Text.Json; using System.Threading.Tasks; using Xunit; @@ -33,7 +34,7 @@ void CaptureSend(IActivity[] arg) { Type = ActivityTypes.Invoke, Name = "task/fetch", - Value = ProtocolJsonSerializer.ToJsonElements(new + Value = ProtocolJsonSerializer.ToObject(new { data = new { @@ -91,7 +92,7 @@ void CaptureSend(IActivity[] arg) { Type = ActivityTypes.Invoke, Name = "task/fetch", - Value = ProtocolJsonSerializer.ToJsonElements(new + Value = ProtocolJsonSerializer.ToObject(new { data = new { @@ -178,7 +179,7 @@ void CaptureSend(IActivity[] arg) { Type = ActivityTypes.Invoke, Name = "task/submit", - Value = ProtocolJsonSerializer.ToJsonElements(new + Value = ProtocolJsonSerializer.ToObject(new { data = new { @@ -236,7 +237,7 @@ void CaptureSend(IActivity[] arg) { Type = ActivityTypes.Invoke, Name = "task/submit", - Value = ProtocolJsonSerializer.ToJsonElements(new + Value = ProtocolJsonSerializer.ToObject(new { data = new { From 18b4dc5e6e805de07d88dac4ff9797d2d8a55b2b Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 11 Feb 2025 09:41:05 -0600 Subject: [PATCH 36/60] More Teams serialization tests --- .../App/ApplicationBuilder.cs | 1 - .../Model/MessagingExtensionActionTests.cs | 97 +++++++++++++++++++ .../Model/MessagingExtensionQueryTests.cs | 42 ++++++++ .../TestJson/MessagingExtensionAction.json | 1 + .../TestJson/MessagingExtensionQuery.json | 1 + 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionAction.json create mode 100644 src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionQuery.json diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs index a9fa18ef..cc2e40f6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using System; -using Microsoft.Agents.Storage; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.State; diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs index fe3e2a04..7369ccec 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionActionTests.cs @@ -2,8 +2,11 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.ComponentModel.Design; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Xunit; namespace Microsoft.Agents.Extensions.Teams.Tests.Model @@ -47,5 +50,99 @@ public void MessagingExtensionActionInitsNoArgs() Assert.NotNull(msgExtAction); Assert.IsType(msgExtAction); } + + [Fact] + public void MessagingExtensionActionRoundTrip() + { + var msgExtAction = new MessagingExtensionAction() + { + CommandId = "commandId", + CommandContext = "commandContext", + BotMessagePreviewAction = "botMessagePreviewAction", + BotActivityPreview = [ + new Activity() { Type = ActivityTypes.Message } + ], + MessagePayload = new MessageActionsPayload() + { + Id = "id", + ReplyToId = "replyToId", + MessageType = "messageType", + CreatedDateTime = "2025-06-15T13:45:30", + LastModifiedDateTime = "2025-07-17T14:46:40", + Deleted = false, + Subject = "subject", + Summary = "summary", + Importance = "importance", + Locale = "locale", + From = new MessageActionsPayloadFrom() + { + User = new MessageActionsPayloadUser() + { + UserIdentityType = "userIdentityType", + Id = "id", + DisplayName = "displayName" + }, + Application = new MessageActionsPayloadApp() + { + ApplicationIdentityType = "applicationIdentityType", + Id = "id", + DisplayName = "displayName" + }, + Conversation = new MessageActionsPayloadConversation() + { + ConversationIdentityType = "conversationIdentityType", + Id = "id", + DisplayName = "displayName" + } + }, + Body = new MessageActionsPayloadBody() + { + ContentType = "contentType", + Content = "content" + }, + AttachmentLayout = "attachmentLayout", + Attachments = [ + new MessageActionsPayloadAttachment() + { + Id = "id", + ContentType = "contentType", + ContentUrl = "contentUrl", + Content = "content", + Name = "name", + ThumbnailUrl = "thumbnailUrl" + } + ], + Mentions = [ + new MessageActionsPayloadMention() + { + Id = 1, + MentionText = "mentionText", + Mentioned = new MessageActionsPayloadFrom() + } + ], + Reactions = [ + new MessageActionsPayloadReaction() + { + ReactionType = "reactionType", + CreatedDateTime = "createdDateTime", + User = new MessageActionsPayloadFrom() + } + ] + } + + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(msgExtAction); + + // Out + var json = ProtocolJsonSerializer.ToJson(msgExtAction); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs index 2990d3be..d2311c63 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/MessagingExtensionQueryTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System.Collections.Generic; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using Microsoft.Agents.Extensions.Teams.Models; using Xunit; @@ -35,5 +37,45 @@ public void MessagingExtensionQueryInitsWithNoArgs() Assert.NotNull(msgExtQuery); Assert.IsType(msgExtQuery); } + + [Fact] + public void MessagingExtensionActionRoundTrip() + { + var msgExtQuery = new MessagingExtensionQuery() + { + CommandId = "commandId", + Parameters = [ + new MessagingExtensionParameter() + { + Name = "name1", + Value = new { param1 = "value1" } + }, + new MessagingExtensionParameter() + { + Name = "name2", + Value = new { param1 = "value2" } + } + ], + QueryOptions = new MessagingExtensionQueryOptions() + { + Skip = 1, + Count = 10 + }, + State = "state" + }; + + // Known good + var goodJson = LoadTestJson.LoadJson(msgExtQuery); + + // Out + var json = ProtocolJsonSerializer.ToJson(msgExtQuery); + Assert.Equal(goodJson, json); + + // In + var inObj = ProtocolJsonSerializer.ToObject(json); + json = ProtocolJsonSerializer.ToJson(inObj); + Assert.Equal(goodJson, json); + } + } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionAction.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionAction.json new file mode 100644 index 00000000..60eb5ed5 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionAction.json @@ -0,0 +1 @@ +{"commandId":"commandId","commandContext":"commandContext","botMessagePreviewAction":"botMessagePreviewAction","botActivityPreview":[{"type":"message"}],"messagePayload":{"id":"id","replyToId":"replyToId","messageType":"messageType","createdDateTime":"2025-06-15T13:45:30","lastModifiedDateTime":"2025-07-17T14:46:40","deleted":false,"subject":"subject","summary":"summary","importance":"importance","locale":"locale","from":{"user":{"userIdentityType":"userIdentityType","id":"id","displayName":"displayName"},"application":{"applicationIdentityType":"applicationIdentityType","id":"id","displayName":"displayName"},"conversation":{"conversationIdentityType":"conversationIdentityType","id":"id","displayName":"displayName"}},"body":{"contentType":"contentType","content":"content"},"attachmentLayout":"attachmentLayout","attachments":[{"id":"id","contentType":"contentType","contentUrl":"contentUrl","content":"content","name":"name","thumbnailUrl":"thumbnailUrl"}],"mentions":[{"id":1,"mentionText":"mentionText","mentioned":{}}],"reactions":[{"reactionType":"reactionType","createdDateTime":"createdDateTime","user":{}}]}} \ No newline at end of file diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionQuery.json b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionQuery.json new file mode 100644 index 00000000..dd0603e8 --- /dev/null +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Model/TestJson/MessagingExtensionQuery.json @@ -0,0 +1 @@ +{"commandId":"commandId","parameters":[{"name":"name1","value":{"param1":"value1"}},{"name":"name2","value":{"param1":"value2"}}],"queryOptions":{"skip":1,"count":10},"state":"state"} \ No newline at end of file From c8be4eab49caa80c9a70bf0e5d1e768a8257287e Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 12 Feb 2025 12:42:46 -0600 Subject: [PATCH 37/60] Dropped BotIdentity and OAuthScope from TurnState --- .../DialogExtensions.cs | 4 +- .../Prompts/OAuthPrompt.cs | 7 +- .../SkillDialog.cs | 4 +- .../App/ApplicationOptions.cs | 1 - .../AssemblyInfo.cs | 1 + .../ChannelAdapter.cs | 10 -- .../ChannelServiceAdapterBase.cs | 27 ++--- .../Compat/ShowTypingMiddleware.cs | 3 +- .../Compat/TypedTurnContext.cs | 3 + .../ITurnContext.cs | 3 + .../ProxyChannelApiHandler.cs | 4 +- .../TurnContext.cs | 3 + .../BotClaims.cs | 30 +++-- src/samples/BotToBot/Bot1/Bots/Bot1.cs | 5 +- .../XUnit/XUnitDialogTestLogger.cs | 4 +- .../BotClaimsTests.cs | 109 ++++++++++-------- .../DialogExtensionsTests.cs | 2 +- .../TestCloudAdapter.cs | 2 + .../Bots/ProactiveBot.cs | 2 +- .../ChannelServiceAdapterBaseTests.cs | 2 +- .../ShowTypingMiddlewareTests.cs | 2 +- .../BotSateTests.cs | 15 --- .../TurnStateTests.cs | 3 - 23 files changed, 125 insertions(+), 121 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs index 280fbeb1..43712396 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/DialogExtensions.cs @@ -181,7 +181,7 @@ private static async Task InnerRunAsync(ITurnContext turnConte /// private static bool SendEoCToParent(ITurnContext turnContext) { - if (turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims)) + if (turnContext.Identity as ClaimsIdentity != null && BotClaims.IsBotClaim(turnContext.Identity)) { // EoC Activities returned by skills are bounced back to the bot by SkillHandler. // In those cases we will have a SkillConversationReference instance in state. @@ -205,7 +205,7 @@ private static bool IsFromParentToSkill(ITurnContext turnContext) return false; } - return turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && BotClaims.IsBotClaim(claimIdentity.Claims); + return turnContext.Identity as ClaimsIdentity != null && BotClaims.IsBotClaim(turnContext.Identity); } // Recursively walk up the DC stack to find the active DC. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 4f094dfa..1d8faa7f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -197,9 +197,8 @@ public override async Task ContinueDialogAsync(DialogContext d // recreate a ConnectorClient and set it in TurnState so replies use the correct one var serviceUrl = dc.Context.Activity.ServiceUrl; - var claimsIdentity = dc.Context.StackState.Get(ChannelAdapter.BotIdentityKey); var audience = callerInfo.Scope; - var connectorClient = await CreateConnectorClientAsync(dc.Context, serviceUrl, claimsIdentity, audience, cancellationToken).ConfigureAwait(false); + var connectorClient = await CreateConnectorClientAsync(dc.Context, serviceUrl, dc.Context.Identity, audience, cancellationToken).ConfigureAwait(false); dc.Context.Services.Set(connectorClient); } } @@ -294,12 +293,12 @@ private static bool IsTokenResponseEvent(ITurnContext turnContext) private static CallerInfo CreateCallerInfo(ITurnContext turnContext) { - if (turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && BotClaims.IsBotClaim(botIdentity.Claims)) + if (turnContext.Identity as ClaimsIdentity != null && BotClaims.IsBotClaim(turnContext.Identity)) { return new CallerInfo { CallerServiceUrl = turnContext.Activity.ServiceUrl, - Scope = BotClaims.GetOutgoingAppId(botIdentity.Claims), + Scope = BotClaims.GetOutgoingAppId(turnContext.Identity), }; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs index 4795e3a6..15477f9a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/SkillDialog.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Client; using Microsoft.Agents.Connector; @@ -9,6 +10,7 @@ using System; using System.Linq; using System.Net.Http; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -377,7 +379,7 @@ private async Task CreateSkillConversationIdAsync(ITurnContext context, // Create a conversationId to interact with the skill and send the activity var conversationIdFactoryOptions = new ConversationIdFactoryOptions { - FromBotOAuthScope = context.StackState.Get(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = context.Identity != null ? BotClaims.GetTokenScopes(context.Identity).First() : null, FromBotId = DialogOptions.BotId, Activity = activity, Bot = DialogOptions.Skill diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index 7b047913..e083d5d0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using System; -using Microsoft.Agents.Storage; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using System.Collections.Generic; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs index bd758961..b760d3fc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/AssemblyInfo.cs @@ -5,6 +5,7 @@ // Allows us to access some internal methods from the Memory.Tests unit tests so we don't have to use reflection and we get compile checks. [assembly: InternalsVisibleTo("Microsoft.Agents.BotBuilder.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.Agents.BotBuilder.Dialogs.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.Agents.Connector.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.Agents.Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.Agents.State.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs index 7794cd79..55c66fa4 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs @@ -22,16 +22,6 @@ public abstract class ChannelAdapter(ILogger logger = null) : IChannelAdapter /// public const string InvokeResponseKey = "ChannelAdapterInvokeResponse"; - /// - /// The string value for the bot identity key. - /// - public const string BotIdentityKey = "BotIdentity"; - - /// - /// The string value for the OAuth scope key. - /// - public const string OAuthScopeKey = "ChannelAdapterOAuthScope"; - /// /// Logger for the bot adapter. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index e1af86cd..8cd289f7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -133,7 +133,7 @@ public override Task ContinueConversationAsync(string botAppId, ConversationRefe _ = reference ?? throw new ArgumentNullException(nameof(reference)); var claims = CreateClaimsIdentity(botAppId); - return ProcessProactiveAsync(CreateClaimsIdentity(botAppId), reference.GetContinuationActivity(), BotClaims.GetTokenAudience(claims.Claims), callback, cancellationToken); + return ProcessProactiveAsync(CreateClaimsIdentity(botAppId), reference.GetContinuationActivity(), BotClaims.GetTokenAudience(claims), callback, cancellationToken); } /// @@ -141,7 +141,7 @@ public override Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, Co { _ = reference ?? throw new ArgumentNullException(nameof(reference)); - return ProcessProactiveAsync(claimsIdentity, reference.GetContinuationActivity(), BotClaims.GetTokenAudience(claimsIdentity.Claims), callback, cancellationToken); + return ProcessProactiveAsync(claimsIdentity, reference.GetContinuationActivity(), BotClaims.GetTokenAudience(claimsIdentity), callback, cancellationToken); } /// @@ -161,7 +161,7 @@ public override Task ContinueConversationAsync(string botAppId, IActivity contin ValidateContinuationActivity(continuationActivity); var claims = CreateClaimsIdentity(botAppId); - return ProcessProactiveAsync(claims, continuationActivity, BotClaims.GetTokenAudience(claims.Claims), callback, cancellationToken); + return ProcessProactiveAsync(claims, continuationActivity, BotClaims.GetTokenAudience(claims), callback, cancellationToken); } /// @@ -214,7 +214,7 @@ public override async Task CreateConversationAsync(string botAppId, string chann using var userTokenClient = await ChannelServiceFactory.CreateUserTokenClientAsync(claimsIdentity, cancellationToken).ConfigureAwait(false); // Create a turn context and run the pipeline. - using var context = CreateTurnContext(createActivity, claimsIdentity, null, connectorClient, userTokenClient, callback); + using var context = CreateTurnContext(createActivity, claimsIdentity, connectorClient, userTokenClient, callback); // Run the pipeline. await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false); @@ -241,7 +241,7 @@ protected async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActiv using var userTokenClient = await ChannelServiceFactory.CreateUserTokenClientAsync(claimsIdentity, cancellationToken).ConfigureAwait(false); // Create a turn context and run the pipeline. - using var context = CreateTurnContext(continuationActivity, claimsIdentity, audience, connectorClient, userTokenClient, callback); + using var context = CreateTurnContext(continuationActivity, claimsIdentity, connectorClient, userTokenClient, callback); // Run the pipeline. await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false); @@ -253,16 +253,12 @@ public override async Task ProcessActivityAsync(ClaimsIdentity c Logger.LogInformation($"ProcessActivityAsync"); IList scopes = null; - string outgoingAudience; - if (BotClaims.IsBotClaim(claimsIdentity.Claims)) + if (BotClaims.IsBotClaim(claimsIdentity)) { - outgoingAudience = BotClaims.GetTokenAudience(claimsIdentity.Claims); - scopes = [$"{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}/.default"]; - activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; + activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity)}"; } else { - outgoingAudience = AuthenticationConstants.BotFrameworkScope; //activity.CallerId = ??? } @@ -274,7 +270,7 @@ public override async Task ProcessActivityAsync(ClaimsIdentity c using var connectorClient = await ChannelServiceFactory.CreateConnectorClientAsync( claimsIdentity, activity.ServiceUrl, - outgoingAudience, + BotClaims.GetTokenAudience(claimsIdentity), cancellationToken, scopes: scopes, useAnonymous: useAnonymousAuthCallback).ConfigureAwait(false); @@ -283,7 +279,7 @@ public override async Task ProcessActivityAsync(ClaimsIdentity c using var userTokenClient = await ChannelServiceFactory.CreateUserTokenClientAsync(claimsIdentity, cancellationToken, useAnonymous: useAnonymousAuthCallback).ConfigureAwait(false); // Create a turn context and run the pipeline. - using var context = CreateTurnContext(activity, claimsIdentity, outgoingAudience, connectorClient, userTokenClient, callback); + using var context = CreateTurnContext(activity, claimsIdentity, connectorClient, userTokenClient, callback); // Run the pipeline. await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false); @@ -325,12 +321,11 @@ private static Activity CreateCreateActivity(ConversationResourceResponse create return (Activity)activity; } - private TurnContext CreateTurnContext(IActivity activity, ClaimsIdentity claimsIdentity, string oauthScope, IConnectorClient connectorClient, IUserTokenClient userTokenClient, BotCallbackHandler callback) + private TurnContext CreateTurnContext(IActivity activity, ClaimsIdentity claimsIdentity, IConnectorClient connectorClient, IUserTokenClient userTokenClient, BotCallbackHandler callback) { var turnContext = new TurnContext(this, activity); - turnContext.StackState.Set(BotIdentityKey, claimsIdentity); - turnContext.StackState.Set(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set + turnContext.Identity = claimsIdentity; turnContext.Services.Set(connectorClient); turnContext.Services.Set(userTokenClient); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index 984c1040..363d9574 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -169,8 +169,7 @@ private async Task FinishTypingTaskAsync(ITurnContext turnContext) private static bool IsSkillBot(ITurnContext turnContext) { - return turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity - && BotClaims.IsBotClaim(claimIdentity.Claims); + return turnContext.Identity as ClaimsIdentity != null && BotClaims.IsBotClaim(turnContext.Identity); } /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs index 930eb85c..fe5d1cf6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/TypedTurnContext.cs @@ -3,6 +3,7 @@ using Microsoft.Agents.Core.Models; using System.Runtime.CompilerServices; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -49,6 +50,8 @@ internal class TypedTurnContext(ITurnContext innerTurnContext) : ITurnContext /// public bool Responded => _innerTurnContext.Responded; + public ClaimsIdentity Identity => _innerTurnContext.Identity; + IActivity ITurnContext.Activity => _innerTurnContext.Activity; /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs index 52a594db..effa45bb 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ITurnContext.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -125,6 +126,8 @@ public interface ITurnContext /// bool Responded { get; } + ClaimsIdentity Identity { get; } + /// /// Sends a message activity to the sender of the incoming activity. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs index b723c216..3a7575c0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs @@ -93,7 +93,7 @@ public async Task OnUpdateActivityAsync(ClaimsIdentity claimsI turnContext.StackState.Set(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = activityId; - turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; + turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity)}"; resourceResponse = await turnContext.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); }); @@ -220,7 +220,7 @@ private async Task ProcessActivityAsync(ClaimsIdentity claimsI turnContext.StackState.Set(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; - turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; + turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity)}"; switch (activity.Type) { case ActivityTypes.EndOfConversation: diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs index a6f9c7eb..e7430d7c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/TurnContext.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -99,6 +100,8 @@ public TurnContext(ITurnContext turnContext, IActivity activity) /// The activity associated with this turn. public IActivity Activity { get; } + public ClaimsIdentity Identity { get; internal set; } + /// /// Gets a value indicating whether at least one response was sent for the current turn. /// diff --git a/src/libraries/Core/Microsoft.Agents.Authentication/BotClaims.cs b/src/libraries/Core/Microsoft.Agents.Authentication/BotClaims.cs index c2547813..0baa8844 100644 --- a/src/libraries/Core/Microsoft.Agents.Authentication/BotClaims.cs +++ b/src/libraries/Core/Microsoft.Agents.Authentication/BotClaims.cs @@ -36,14 +36,14 @@ public static string GetAppId(ClaimsIdentity claimsIdentity) /// If the is not present, this method will attempt to /// obtain the attribute from the or if present. /// - /// A list of instances. + /// The bot identity /// The value of the appId claim if found (null if it can't find a suitable claim). - public static string GetOutgoingAppId(IEnumerable claims) + public static string GetOutgoingAppId(ClaimsIdentity identity) { // Verify we have a sensible Claims Identity - ArgumentNullException.ThrowIfNull(claims); + ArgumentNullException.ThrowIfNull(identity); - var claimsList = claims as IList ?? claims.ToList(); + var claimsList = identity.Claims; string appId = null; // Depending on Version, the is either in the @@ -84,9 +84,11 @@ public static string GetOutgoingAppId(IEnumerable claims) /// /// A list of claims. /// True if the list of claims is a bot claim, false if is not. - public static bool IsBotClaim(IEnumerable claims) + public static bool IsBotClaim(ClaimsIdentity claims) { - var claimsList = claims.ToList(); + ArgumentNullException.ThrowIfNull(claims); + + var claimsList = claims.Claims; var version = claimsList.FirstOrDefault(claim => claim.Type == AuthenticationConstants.VersionClaim); if (string.IsNullOrWhiteSpace(version?.Value)) @@ -102,7 +104,7 @@ public static bool IsBotClaim(IEnumerable claims) return false; } - var appId = GetOutgoingAppId(claimsList); + var appId = GetOutgoingAppId(claims); if (string.IsNullOrWhiteSpace(appId)) { return false; @@ -112,10 +114,18 @@ public static bool IsBotClaim(IEnumerable claims) return appId != audience; } - public static string GetTokenAudience(IEnumerable claims) + public static string GetTokenAudience(ClaimsIdentity identity) + { + return BotClaims.IsBotClaim(identity) + ? $"app://{BotClaims.GetOutgoingAppId(identity)}" + : AuthenticationConstants.BotFrameworkScope; + } + + public static IList GetTokenScopes(ClaimsIdentity identity) { - // Temporarily setting audience to api uri so that MSAL works. This needs to be resolved. - return $"app://{BotClaims.GetOutgoingAppId(claims)}"; + return BotClaims.IsBotClaim(identity) + ? [$"{BotClaims.GetOutgoingAppId(identity)}/.default"] + : null; } } } diff --git a/src/samples/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/BotToBot/Bot1/Bots/Bot1.cs index ba018fb8..c3d41474 100644 --- a/src/samples/BotToBot/Bot1/Bots/Bot1.cs +++ b/src/samples/BotToBot/Bot1/Bots/Bot1.cs @@ -17,6 +17,7 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.BotBuilder.Compat; +using System.Linq; namespace Microsoft.Agents.Samples.Bots { @@ -125,7 +126,7 @@ private async Task SendToBot(ITurnContext turnContext, IChannelInfo targetChanne // Create a conversationId to interact with the skill and send the activity var options = new ConversationIdFactoryOptions { - FromBotOAuthScope = turnContext.StackState.Get(ChannelAdapter.OAuthScopeKey), + FromBotOAuthScope = BotClaims.GetTokenScopes(turnContext.Identity).First(), FromBotId = _channelHost.HostAppId, Activity = turnContext.Activity, Bot = targetChannel @@ -169,7 +170,7 @@ private async Task ProcessActivityAsync(ClaimsIdentity claimsI { activity.ApplyConversationReference(botConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; - turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity.Claims)}"; + turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{BotClaims.GetOutgoingAppId(claimsIdentity)}"; if (activity.Type == ActivityTypes.EndOfConversation) { diff --git a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs index 1f9049b2..9d700538 100644 --- a/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs +++ b/src/tests/BotBuilder.Testing/XUnit/XUnitDialogTestLogger.cs @@ -56,7 +56,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) { var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); - context.StackState.Set(_stopWatchStateKey, stopwatch); + context.Services.Set(_stopWatchStateKey, stopwatch); await LogIncomingActivityAsync(context, context.Activity, cancellationToken).ConfigureAwait(false); context.OnSendActivities(OnSendActivitiesAsync); @@ -98,7 +98,7 @@ public XUnitDialogTestLogger(ITestOutputHelper xunitOutputHelper) /// A task that represents the work to execute. protected virtual Task LogOutgoingActivityAsync(ITurnContext context, IActivity activity, CancellationToken cancellationToken = default(CancellationToken)) { - var stopwatch = context.StackState.Get(_stopWatchStateKey); + var stopwatch = context.Services.Get(_stopWatchStateKey); var actor = "Bot: "; if (activity.Type == ActivityTypes.Message) { diff --git a/src/tests/Microsoft.Agents.Auth.Tests/BotClaimsTests.cs b/src/tests/Microsoft.Agents.Auth.Tests/BotClaimsTests.cs index 0c70da08..914fc244 100644 --- a/src/tests/Microsoft.Agents.Auth.Tests/BotClaimsTests.cs +++ b/src/tests/Microsoft.Agents.Auth.Tests/BotClaimsTests.cs @@ -124,10 +124,10 @@ public void GetOutgoingAppId_ThrowIfNull() [Fact] public void GetOutgoingAppId_NoVersionClaim() { - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(ClaimTypes.Name, "Name1") - }; + ]); Assert.Null(BotClaims.GetOutgoingAppId(claims)); } @@ -135,11 +135,11 @@ public void GetOutgoingAppId_NoVersionClaim() [Fact] public void GetOutgoingAppId_MultipleVersionClaims() { - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "claim1"), new(AuthenticationConstants.VersionClaim, "claim2") - }; + ]); Assert.Null(BotClaims.GetOutgoingAppId(claims)); } @@ -147,10 +147,10 @@ public void GetOutgoingAppId_MultipleVersionClaims() [Fact] public void GetOutgoingAppId_InvalidVersionNumber() { - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "3.0"), - }; + ]); Assert.Null(BotClaims.GetOutgoingAppId(claims)); } @@ -159,10 +159,10 @@ public void GetOutgoingAppId_InvalidVersionNumber() public void GetOutgoingAppId_ValidVersionClaimNullVersionNoAppId() { // this tests sets up a claimset with a version claim with an empty string. There is no AppIdClaim, so this should fail. - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, string.Empty), - }; + ]); Assert.Null(BotClaims.GetOutgoingAppId(claims)); } @@ -170,11 +170,11 @@ public void GetOutgoingAppId_ValidVersionClaimNullVersionNoAppId() public void GetOutgoingAppId_ValidVersionClaimNullVersionAppId() { // this tests sets up a claimset with a empty version and an AppId Claim. This is a valid combination. - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, string.Empty), new(AuthenticationConstants.AppIdClaim, "appId") - }; + ]); Assert.Equal("appId", BotClaims.GetOutgoingAppId(claims)); } @@ -183,11 +183,11 @@ public void GetOutgoingAppId_ValidVersionClaimNullVersionAppId() public void GetOutgoingAppId_ValidV1VersionAppId() { // this tests sets up a claimset with a version of v1 and an AppId Claim. This is a valid combination. - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "1.0"), new(AuthenticationConstants.AppIdClaim, "appId") - }; + ]); Assert.Equal("appId", BotClaims.GetOutgoingAppId(claims)); } @@ -196,11 +196,11 @@ public void GetOutgoingAppId_ValidV1VersionAppId() public void GetOutgoingAppId_ValidV2VersionWrongClaim() { // this tests sets up a claimset with a version claim with an empty string. There is no AppIdClaim, so this should fail. - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), new(AuthenticationConstants.AppIdClaim, "appId") // not a valid claim on a v2 token - }; + ]); Assert.Null(BotClaims.GetOutgoingAppId(claims)); } @@ -209,14 +209,14 @@ public void GetOutgoingAppId_ValidV2VersionWrongClaim() [Fact] public void GetOutgoingAppId_ValidV2VersionClaimWithAuthorizedParty() { - static IEnumerable GetClaims() // Use this function to cover the ToList() method. - { + var claims = new ClaimsIdentity( + [ // this tests sets up a claimset with a version claim "2.0" and an AuthorizedParty Claim. - yield return new Claim(AuthenticationConstants.VersionClaim, "2.0"); - yield return new Claim(AuthenticationConstants.AuthorizedParty, "appId"); - }; + new(AuthenticationConstants.VersionClaim, "2.0"), + new(AuthenticationConstants.AuthorizedParty, "appId") + ]); - Assert.Equal("appId", BotClaims.GetOutgoingAppId(GetClaims())); + Assert.Equal("appId", BotClaims.GetOutgoingAppId(claims)); } [Fact] @@ -229,12 +229,12 @@ public void IsBotClaim_ThrowOnNullClaimset() public void IsBotClaim_ValidClaimset() { // Setup a valid claim set - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), new(AuthenticationConstants.AuthorizedParty, "party"), new(AuthenticationConstants.AudienceClaim, "audience") - }; + ]); Assert.True(BotClaims.IsBotClaim(claims)); } @@ -243,12 +243,12 @@ public void IsBotClaim_ValidClaimset() public void IsBotClaim_InvalidClaimset() { // Setup a valid claim set - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), new(AuthenticationConstants.AuthorizedParty, "party"), // if these 2 match, the claim set doesn't qualify new(AuthenticationConstants.AudienceClaim, "party") // if these 2 match, the claim set doesn't qualify - }; + ]); Assert.False(BotClaims.IsBotClaim(claims)); } @@ -257,14 +257,14 @@ public void IsBotClaim_InvalidClaimset() public void IsBotClaim_InvalidClaimsetByAudiance() { // Setup a valid claim set - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), new(AuthenticationConstants.AuthorizedParty, "party"), // For some reason, this is invalid. Coding it here to make sure behavior doesn't change. new(AuthenticationConstants.AudienceClaim, AuthenticationConstants.BotFrameworkTokenIssuer) - }; + ]); Assert.False(BotClaims.IsBotClaim(claims)); } @@ -272,11 +272,11 @@ public void IsBotClaim_InvalidClaimsetByAudiance() [Fact] public void IsBotClaim_ShouldReturnFalseOnNoVersion() { - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.AudienceClaim, "audience"), new(AuthenticationConstants.AppIdClaim, "appId") - }; + ]); Assert.False(BotClaims.IsBotClaim(claims)); } @@ -285,28 +285,43 @@ public void IsBotClaim_ShouldReturnFalseOnNoVersion() public void IsBotClaim_ShouldReturnFalseOnNoOutgoingAppId() { // Setup an invalid claim set - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), new(AuthenticationConstants.AudienceClaim, "audience"), new(AuthenticationConstants.AppIdClaim, "appId") // not a valid claim on a v2 token - }; + ]); Assert.False(BotClaims.IsBotClaim(claims)); } [Fact] - public void GetTokenAudience_ShouldSetAudienceInUri() + public void GetTokenAudience_ForBot() { - var claims = new List - { + var claims = new ClaimsIdentity( + [ new(AuthenticationConstants.VersionClaim, "2.0"), - new(AuthenticationConstants.AuthorizedParty, "appId") - }; + new(AuthenticationConstants.AuthorizedParty, "appId"), + new(AuthenticationConstants.AudienceClaim, "aud") + ]); var audience = BotClaims.GetTokenAudience(claims); Assert.Equal("app://appId", audience); } + + [Fact] + public void GetTokenAudience_ForABS() + { + var claims = new ClaimsIdentity( + [ + new(AuthenticationConstants.VersionClaim, "2.0"), + new(AuthenticationConstants.AuthorizedParty, "appId"), + ]); + + var audience = BotClaims.GetTokenAudience(claims); + + Assert.Equal(AuthenticationConstants.BotFrameworkScope, audience); + } } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs index 1d4bccfd..453e5d85 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/DialogExtensionsTests.cs @@ -184,7 +184,7 @@ private TestFlow CreateTestFlow(Dialog dialog, FlowTestCase testCase, string loc claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); - turnContext.StackState.Set(ChannelAdapter.BotIdentityKey, claimsIdentity); + ((TurnContext)turnContext).Identity = claimsIdentity; if (testCase == FlowTestCase.RootBotConsumingSkill) { diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs index 741cac6f..fb16965c 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/TestCloudAdapter.cs @@ -47,6 +47,8 @@ public InterceptTurnContext(TestCloudAdapter testAdapter, ITurnContext innerTurn public bool Responded => _innerTurnContext.Responded; + public ClaimsIdentity Identity => _innerTurnContext.Identity; + public IConnectorClient Connector => throw new System.NotImplementedException(); public ITurnContext OnDeleteActivity(DeleteActivityHandler handler) diff --git a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs index 7899a85e..61ce7871 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.TestBot.Shared/Bots/ProactiveBot.cs @@ -16,7 +16,7 @@ public class ProactiveBot : ActivityHandler { protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { - var claimsIdentity = turnContext.StackState.Get(ChannelAdapter.BotIdentityKey) as ClaimsIdentity; + var claimsIdentity = turnContext.Identity as ClaimsIdentity; var botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs index 6cfd2d9c..b83be5f2 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelServiceAdapterBaseTests.cs @@ -245,7 +245,7 @@ public async Task SendActivitiesAsync_ShouldDelayTasks() var connectorClient = CreateMockConnectorClient(); var adapter = new TestChannelAdapter(new Mock().Object); var context = new TurnContext(adapter, new Activity()); - context.StackState.Set(connectorClient.Object); + context.Services.Set(connectorClient.Object); var activities = new Activity[] { new Activity(type: ActivityTypes.Delay, value: 2000) diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs index 02d2a398..c1adf3f2 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ShowTypingMiddlewareTests.cs @@ -150,7 +150,7 @@ public override TurnContext CreateTurnContext(IActivity activity) claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _parentBotId)); claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _skillBotId)); - turnContext.StackState.Set(BotIdentityKey, claimsIdentity); + turnContext.Identity = claimsIdentity; return turnContext; } diff --git a/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs b/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs index cd2fe862..501ee15a 100644 --- a/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/BotSateTests.cs @@ -373,21 +373,6 @@ public async Task LoadSaveDelete() Assert.Null(obj2["property-b"]); } - [Fact] - public async Task State_DoNOTRememberContextState() - { - var adapter = new TestAdapter(TestAdapter.CreateConversation("State_DoNOTRememberContextState")); - - await new TestFlow(adapter, (context, cancellationToken) => - { - var obj = context.StackState.Get(); - Assert.Null(obj); - return Task.CompletedTask; - }) - .Send("set value") - .StartTestAsync(); - } - [Fact] public async Task State_RememberIStoreItemUserState() { diff --git a/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs index 85215e00..40d6f475 100644 --- a/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs @@ -74,9 +74,6 @@ public void TurnState_TempState() turnState.Temp.SetValue("count", 2); count = turnState.GetValue("temp.count"); Assert.Equal(2, count); - - turnState.SetValue($"temp.{ChannelAdapter.OAuthScopeKey}", "botscope"); - Assert.Equal("botscope", turnState.Temp.GetValue(ChannelAdapter.OAuthScopeKey)); } /* From 924452f4c1d5c574ac28702c860f357460e04b4b Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 12 Feb 2025 12:48:39 -0600 Subject: [PATCH 38/60] Uncommented IInputFileDownloader handling --- .../App/Application.cs | 9 ++++----- .../State/TempState.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index b1106f11..c00a9eea 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -6,6 +6,7 @@ using Microsoft.Agents.Core.Models; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -626,18 +627,16 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } */ - /* // Download any input files - IList>? fileDownloaders = this.Options.FileDownloaders; + IList? fileDownloaders = this.Options.FileDownloaders; if (fileDownloaders != null && fileDownloaders.Count > 0) { - foreach (IInputFileDownloader downloader in fileDownloaders) + foreach (IInputFileDownloader downloader in fileDownloaders) { - List files = await downloader.DownloadFilesAsync(turnContext, turnState); + List files = await downloader.DownloadFilesAsync(turnContext, turnState, cancellationToken).ConfigureAwait(false); turnState.Temp.InputFiles.AddRange(files); } } - */ bool eventHandlerCalled = false; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs index b837e21d..57a2fb88 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.App; using System; using System.Collections.Generic; using System.Threading; @@ -10,11 +11,25 @@ namespace Microsoft.Agents.BotBuilder.State { public class TempState : IBotState { + /// + /// Name of the input files key + /// + public const string InputFilesKey = "inputFiles"; + public static readonly string ScopeName = "temp"; private readonly Dictionary _state = []; public string Name => ScopeName; + /// + /// Downloaded files included in the Activity + /// + public List InputFiles + { + get => GetValue>(InputFilesKey)!; + set => SetValue(InputFilesKey, value); + } + public void ClearState() { _state.Clear(); From 7cb4ca6683d57fd53b8a25219f93a73779b5b369 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 12 Feb 2025 16:11:25 -0600 Subject: [PATCH 39/60] TeamsConnectorClient reversion to creating per call --- .../IRestTransport.cs | 15 +++ .../RestClients/AttachmentsRestClient.cs | 15 +-- .../RestClients/BotSignInRestClient.cs | 15 +-- .../RestClients/ConversationsRestClient.cs | 62 +++++----- .../RestClients/RestClientBase.cs | 7 +- .../RestClients/UserTokenRestClient.cs | 32 +++-- .../RestConnectorClient.cs | 13 +-- .../RestUserTokenClient.cs | 15 ++- .../Connector/ITeamsConnectorClient.cs | 4 +- .../Connector/RestTeamsConnectorClient.cs | 17 +-- .../Connector/RestTeamsOperations.cs | 11 +- .../Connector/TeamsInfo.cs | 6 +- .../TeamsChannelServiceClientFactory.cs | 109 ------------------ .../AspNetCore/ServiceCollectionExtensions.cs | 12 +- .../Teams/AdaptiveCardActions/Program.cs | 3 +- src/samples/Teams/ConversationBot/Program.cs | 3 +- src/samples/Teams/LinkUnfurling/Program.cs | 3 +- .../Teams/Meetings-Notification/Program.cs | 3 +- .../MessagingExtensionsSearch/Program.cs | 3 +- src/samples/Teams/TaskModule/Program.cs | 3 +- .../Program.cs | 3 +- .../Program.cs | 3 +- .../Program.cs | 3 +- src/samples/Teams/bot-tag-mention/Program.cs | 3 +- .../Teams/bot-teams-authentication/Program.cs | 3 +- .../AttachmentsRestClientTests.cs | 27 ++--- .../BotSignInRestClientTests.cs | 20 ++-- .../ConversationsRestClientTests.cs | 26 ++--- .../UserTokenRestClientTests.cs | 70 +++++------ .../Connector/RestTeamsOperationsTests.cs | 18 +-- 30 files changed, 190 insertions(+), 337 deletions(-) create mode 100644 src/libraries/Client/Microsoft.Agents.Connector/IRestTransport.cs delete mode 100644 src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs diff --git a/src/libraries/Client/Microsoft.Agents.Connector/IRestTransport.cs b/src/libraries/Client/Microsoft.Agents.Connector/IRestTransport.cs new file mode 100644 index 00000000..8df79b57 --- /dev/null +++ b/src/libraries/Client/Microsoft.Agents.Connector/IRestTransport.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Agents.Connector +{ + public interface IRestTransport + { + Uri Endpoint { get; } + Task GetHttpClientAsync(); + } +} diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs index b6deee7f..bf78ee05 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs @@ -14,18 +14,15 @@ namespace Microsoft.Agents.Connector.RestClients { - internal class AttachmentsRestClient(Uri endpoint, - IHttpClientFactory httpClientFactory, - Func> tokenProviderFunction, - string httpClientName = nameof(RestUserTokenClient)) : RestClientBase(httpClientFactory, httpClientName, tokenProviderFunction), IAttachments + internal class AttachmentsRestClient(IRestTransport transport) : IAttachments { - private readonly Uri _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); internal HttpRequestMessage CreateGetAttachmentInfoRequest(string attachmentId) { var request = new HttpRequestMessage(); request.Method = HttpMethod.Get; - request.RequestUri = new Uri(endpoint, $"v3/attachments/{attachmentId}"); + request.RequestUri = new Uri(_transport.Endpoint, $"v3/attachments/{attachmentId}"); request.Headers.Add("Accept", "application/json"); return request; } @@ -43,7 +40,7 @@ public async Task GetAttachmentInfoAsync(string attachmentId, Ca } using var message = CreateGetAttachmentInfoRequest(attachmentId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int) httpResponse.StatusCode) { @@ -75,7 +72,7 @@ internal HttpRequestMessage CreateGetAttachmentRequest(string attachmentId, stri { var request = new HttpRequestMessage(); request.Method = HttpMethod.Get; - request.RequestUri = new Uri(endpoint, $"v3/attachments/{attachmentId}/views/{viewId}"); + request.RequestUri = new Uri(_transport.Endpoint, $"v3/attachments/{attachmentId}/views/{viewId}"); request.Headers.Add("Accept", "application/octet-stream, application/json"); return request; } @@ -98,7 +95,7 @@ public async Task GetAttachmentAsync(string attachmentId, string viewId, } using var message = CreateGetAttachmentRequest(attachmentId, viewId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/BotSignInRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/BotSignInRestClient.cs index a747d7e0..39f7c198 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/BotSignInRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/BotSignInRestClient.cs @@ -12,12 +12,9 @@ namespace Microsoft.Agents.Connector.RestClients { - internal class BotSignInRestClient(Uri endpoint, - IHttpClientFactory httpClientFactory, - Func> tokenProviderFunction, - string httpClientName = nameof(RestUserTokenClient)) : RestClientBase(httpClientFactory, httpClientName, tokenProviderFunction), IBotSignIn + internal class BotSignInRestClient(IRestTransport transport) : IBotSignIn { - private readonly Uri _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); internal HttpRequestMessage CreateGetSignInUrlRequest(string state, string codeChallenge, string emulatorUrl, string finalRedirect) { @@ -25,7 +22,7 @@ internal HttpRequestMessage CreateGetSignInUrlRequest(string state, string codeC { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint, $"api/botsignin/GetSignInUrl") + RequestUri = new Uri(_transport.Endpoint, $"api/botsignin/GetSignInUrl") .AppendQuery("state", state) .AppendQuery("code_challenge", codeChallenge) .AppendQuery("emulatorUrl", emulatorUrl) @@ -42,7 +39,7 @@ public async Task GetSignInUrlAsync(string state, string codeChallenge = ArgumentException.ThrowIfNullOrEmpty(state); using var message = CreateGetSignInUrlRequest(state, codeChallenge, emulatorUrl, finalRedirect); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -61,7 +58,7 @@ internal HttpRequestMessage CreateGetSignInResourceRequest(string state, string { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint, $"api/botsignin/GetSignInResource") + RequestUri = new Uri(_transport.Endpoint, $"api/botsignin/GetSignInResource") .AppendQuery("state", state) .AppendQuery("code_challenge", codeChallenge) .AppendQuery("emulatorUrl", emulatorUrl) @@ -78,7 +75,7 @@ public async Task GetSignInResourceAsync(string state, string co ArgumentException.ThrowIfNullOrEmpty(state); using var message = CreateGetSignInResourceRequest(state, codeChallenge, emulatorUrl, finalRedirect); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/ConversationsRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/ConversationsRestClient.cs index 3750fcda..c6add8a7 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/ConversationsRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/ConversationsRestClient.cs @@ -15,15 +15,9 @@ namespace Microsoft.Agents.Connector.RestClients { - internal class ConversationsRestClient( - Uri endpoint, - IHttpClientFactory httpClientFactory, - Func> tokenProviderFunction, - string httpClientName = nameof(RestUserTokenClient)) : RestClientBase(httpClientFactory, httpClientName, tokenProviderFunction), IConversations + internal class ConversationsRestClient(IRestTransport transport) : IConversations { - private readonly Uri _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); - - + private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); internal HttpRequestMessage CreateGetConversationsRequest(string continuationToken) { @@ -31,7 +25,7 @@ internal HttpRequestMessage CreateGetConversationsRequest(string continuationTok { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations") .AppendQuery("continuationToken", continuationToken) }; @@ -43,7 +37,7 @@ internal HttpRequestMessage CreateGetConversationsRequest(string continuationTok public async Task GetConversationsAsync(string continuationToken = null, CancellationToken cancellationToken = default) { using var message = CreateGetConversationsRequest(continuationToken); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -78,7 +72,7 @@ internal HttpRequestMessage CreateCreateConversationRequest(ConversationParamete var request = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -92,7 +86,7 @@ internal HttpRequestMessage CreateCreateConversationRequest(ConversationParamete public async Task CreateConversationAsync(ConversationParameters body = null, CancellationToken cancellationToken = default) { using var message = CreateCreateConversationRequest(body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -127,7 +121,7 @@ internal HttpRequestMessage CreateSendToConversationRequest(string conversationI var request = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -148,7 +142,7 @@ public async Task SendToConversationAsync(string conversationI ArgumentNullException.ThrowIfNullOrEmpty(conversationId); using var message = CreateSendToConversationRequest(conversationId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -183,7 +177,7 @@ internal HttpRequestMessage CreateSendConversationHistoryRequest(string conversa var request = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/history") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/history") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -199,7 +193,7 @@ public async Task SendConversationHistoryAsync(string conversa ArgumentException.ThrowIfNullOrEmpty(conversationId); using var message = CreateSendConversationHistoryRequest(conversationId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -234,7 +228,7 @@ internal HttpRequestMessage CreateUpdateActivityRequest(string conversationId, s var request = new HttpRequestMessage { Method = HttpMethod.Put, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -256,7 +250,7 @@ public async Task UpdateActivityAsync(string conversationId, s ArgumentException.ThrowIfNullOrEmpty(activityId); using var message = CreateUpdateActivityRequest(conversationId, activityId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -291,7 +285,7 @@ internal HttpRequestMessage CreateReplyToActivityRequest(string conversationId, var request = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -315,7 +309,7 @@ public async Task ReplyToActivityAsync(string conversationId, ArgumentException.ThrowIfNullOrEmpty(activityId); using var message = CreateReplyToActivityRequest(conversationId, activityId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -355,7 +349,7 @@ internal HttpRequestMessage CreateDeleteActivityRequest(string conversationId, s var request = new HttpRequestMessage { Method = HttpMethod.Delete, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}") }; request.Headers.Add("Accept", "application/json"); return request; @@ -368,7 +362,7 @@ public async Task DeleteActivityAsync(string conversationId, string activityId, ArgumentException.ThrowIfNullOrEmpty(activityId); using var message = CreateDeleteActivityRequest(conversationId, activityId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -400,7 +394,7 @@ internal HttpRequestMessage CreateGetConversationMembersRequest(string conversat var request = new HttpRequestMessage { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members") }; request.Headers.Add("Accept", "application/json"); return request; @@ -412,7 +406,7 @@ public async Task> GetConversationMembersAsync(str ArgumentException.ThrowIfNullOrEmpty(conversationId); using var message = CreateGetConversationMembersRequest(conversationId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -445,7 +439,7 @@ internal HttpRequestMessage CreateGetConversationMemberRequest(string conversati var request = new HttpRequestMessage { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members/{userId}") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members/{userId}") }; request.Headers.Add("Accept", "application/json"); return request; @@ -458,7 +452,7 @@ public async Task GetConversationMemberAsync(string userId, stri ArgumentException.ThrowIfNullOrEmpty(userId); using var message = CreateGetConversationMemberRequest(conversationId, userId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -491,7 +485,7 @@ internal HttpRequestMessage CreateDeleteConversationMemberRequest(string convers var request = new HttpRequestMessage { Method = HttpMethod.Delete, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members/{memberId}") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/members/{memberId}") }; request.Headers.Add("Accept", "application/json"); return request; @@ -504,7 +498,7 @@ public async Task DeleteConversationMemberAsync(string conversationId, string me ArgumentException.ThrowIfNullOrEmpty(memberId); using var message = CreateDeleteConversationMemberRequest(conversationId, memberId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -535,7 +529,7 @@ internal HttpRequestMessage CreateGetConversationPagedMembersRequest(string conv var request = new HttpRequestMessage(); request.Method = HttpMethod.Get; - request.RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/pagedmembers") + request.RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/pagedmembers") .AppendQuery("pageSize", pageSize.HasValue ? pageSize.Value.ToString() : null) .AppendQuery("continuationToken", continuationToken); @@ -549,7 +543,7 @@ public async Task GetConversationPagedMembersAsync(string co ArgumentException.ThrowIfNullOrEmpty(conversationId); using var message = CreateGetConversationPagedMembersRequest(conversationId, pageSize, continuationToken); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -582,7 +576,7 @@ internal HttpRequestMessage CreateGetActivityMembersRequest(string conversationI var request = new HttpRequestMessage { Method = HttpMethod.Get, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}/members") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/activities/{activityId}/members") }; request.Headers.Add("Accept", "application/json"); return request; @@ -595,7 +589,7 @@ public async Task> GetActivityMembersAsync(string ArgumentException.ThrowIfNullOrEmpty(activityId); using var message = CreateGetActivityMembersRequest(conversationId, activityId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -628,7 +622,7 @@ internal HttpRequestMessage CreateUploadAttachmentRequest(string conversationId, var request = new HttpRequestMessage { Method = HttpMethod.Post, - RequestUri = new Uri(endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/attachments") + RequestUri = new Uri(_transport.Endpoint.EnsureTrailingSlash(), $"v3/conversations/{conversationId}/attachments") }; request.Headers.Add("Accept", "application/json"); if (body != null) @@ -644,7 +638,7 @@ public async Task UploadAttachmentAsync(string conversationId, ArgumentException.ThrowIfNullOrEmpty(conversationId); using var message = CreateUploadAttachmentRequest(conversationId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs index c3f625b6..4d4a8437 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/RestClientBase.cs @@ -8,13 +8,16 @@ namespace Microsoft.Agents.Connector.RestClients { - public class RestClientBase(IHttpClientFactory httpClientFactory, string httpClientName, Func> tokenProviderFunction) + public class RestClientBase(Uri endpoint, IHttpClientFactory httpClientFactory, string httpClientName, Func> tokenProviderFunction) : IRestTransport { private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); private readonly Func> _tokenProviderFunction = tokenProviderFunction; private readonly string _httpClientName = httpClientName ?? throw new ArgumentNullException(nameof(httpClientName)); + private readonly Uri _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); - protected async Task GetHttpClientAsync() + public Uri Endpoint => _endpoint; + + public async Task GetHttpClientAsync() { var httpClient = _httpClientFactory.CreateClient(_httpClientName); diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs index 23cf9ecf..f329bbc3 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs @@ -14,20 +14,16 @@ namespace Microsoft.Agents.Connector.RestClients { - internal class UserTokenRestClient( - Uri endpoint, - IHttpClientFactory httpClientFactory, - Func> tokenProviderFunction, - string httpClientName = nameof(RestUserTokenClient)) : RestClientBase(httpClientFactory, httpClientName, tokenProviderFunction), IUserToken + internal class UserTokenRestClient(IRestTransport transport) : IUserToken { - private readonly Uri _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); internal HttpRequestMessage CreateGetTokenRequest(string userId, string connectionName, string channelId, string code) { var request = new HttpRequestMessage(); request.Method = HttpMethod.Get; - request.RequestUri = new Uri(_endpoint, $"api/usertoken/GetToken") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/GetToken") .AppendQuery("userId", userId) .AppendQuery("connectionName", connectionName) .AppendQuery("channelId", channelId) @@ -42,7 +38,7 @@ internal HttpRequestMessage CreateExchangeRequest(string userId, string connecti var request = new HttpRequestMessage(); request.Method = HttpMethod.Post; - request.RequestUri = new Uri(_endpoint, $"api/usertoken/exchange") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/exchange") .AppendQuery("userId", userId) .AppendQuery("connectionName", connectionName) .AppendQuery("channelId", channelId); @@ -64,7 +60,7 @@ public async Task ExchangeAsyncAsync(string userId, string connectionNam ArgumentNullException.ThrowIfNull(exchangeRequest); using var message = CreateExchangeRequest(userId, connectionName, channelId, exchangeRequest); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -95,7 +91,7 @@ public async Task GetTokenAsync(string userId, string connectionN ArgumentException.ThrowIfNullOrEmpty(connectionName); using var message = CreateGetTokenRequest(userId, connectionName, channelId, code); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -113,7 +109,7 @@ internal HttpRequestMessage CreateGetAadTokensRequest(string userId, string conn var request = new HttpRequestMessage(); request.Method = HttpMethod.Post; - request.RequestUri = new Uri(endpoint, $"api/usertoken/GetAadTokens") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/GetAadTokens") .AppendQuery("userId", userId) .AppendQuery("connectionName", connectionName) .AppendQuery("channelId", channelId); @@ -133,7 +129,7 @@ public async Task> GetAadTokensAsync( ArgumentException.ThrowIfNullOrEmpty(connectionName); using var message = CreateGetAadTokensRequest(userId, connectionName, channelId, aadResourceUrls); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -151,7 +147,7 @@ internal HttpRequestMessage CreateSignOutRequest(string userId, string connectio var request = new HttpRequestMessage(); request.Method = HttpMethod.Delete; - request.RequestUri = new Uri(endpoint, $"api/usertoken/SignOut") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/SignOut") .AppendQuery("userId", userId) .AppendQuery("connectionName", connectionName) .AppendQuery("channelId", channelId); @@ -166,7 +162,7 @@ public async Task SignOutAsync(string userId, string connectionName = nu ArgumentException.ThrowIfNullOrEmpty(userId); using var message = CreateSignOutRequest(userId, connectionName, channelId); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -186,7 +182,7 @@ internal HttpRequestMessage CreateGetTokenStatusRequest(string userId, string ch var request = new HttpRequestMessage(); request.Method = HttpMethod.Get; - request.RequestUri = new Uri(endpoint, $"api/usertoken/GetTokenStatus") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/GetTokenStatus") .AppendQuery("userId", userId) .AppendQuery("channelId", channelId) .AppendQuery("include", include); @@ -201,7 +197,7 @@ public async Task> GetTokenStatusAsync(string userId, ArgumentException.ThrowIfNullOrEmpty(userId); using var message = CreateGetTokenStatusRequest(userId, channelId, include); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { @@ -219,7 +215,7 @@ internal HttpRequestMessage CreateExchangeTokenRequest(string userId, string con var request = new HttpRequestMessage(); request.Method = HttpMethod.Post; - request.RequestUri = new Uri(endpoint, $"api/usertoken/exchange") + request.RequestUri = new Uri(_transport.Endpoint, $"api/usertoken/exchange") .AppendQuery("userId", userId) .AppendQuery("connectionName", connectionName) .AppendQuery("channelId", channelId); @@ -240,7 +236,7 @@ public async Task ExchangeTokenAsync(string userId, string connec ArgumentException.ThrowIfNullOrEmpty(channelId); using var message = CreateExchangeTokenRequest(userId, connectionName, channelId, body); - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(message, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestConnectorClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestConnectorClient.cs index 8e902d63..5d8291bb 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestConnectorClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestConnectorClient.cs @@ -12,25 +12,22 @@ namespace Microsoft.Agents.Connector /// The Bot Connector REST API allows your bot to send and receive messages to channels configured in the Azure Bot Service. /// The Connector service uses industry-standard REST and JSON over HTTPS. /// - public class RestConnectorClient : IConnectorClient + public class RestConnectorClient : RestClientBase, IConnectorClient { - private readonly Uri _endpoint; - public IAttachments Attachments { get; } public IConversations Conversations { get; } - public Uri BaseUri => _endpoint; + public Uri BaseUri => base.Endpoint; public RestConnectorClient(Uri endpoint, IHttpClientFactory httpClientFactory, Func> tokenProviderFunction, string namedClient = nameof(RestConnectorClient)) + : base(endpoint, httpClientFactory, namedClient, tokenProviderFunction) { ArgumentNullException.ThrowIfNull(endpoint); ArgumentNullException.ThrowIfNull(httpClientFactory); - _endpoint = endpoint; - - Conversations = new ConversationsRestClient(endpoint, httpClientFactory, tokenProviderFunction, namedClient); - Attachments = new AttachmentsRestClient(endpoint, httpClientFactory, tokenProviderFunction, namedClient); + Conversations = new ConversationsRestClient(this); + Attachments = new AttachmentsRestClient(this); } public void Dispose() diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestUserTokenClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestUserTokenClient.cs index a696f104..1319e46f 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestUserTokenClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestUserTokenClient.cs @@ -20,26 +20,25 @@ namespace Microsoft.Agents.Connector /// /// Client for OAuth flow using the TokenAPI endpoints. This uses the Azure Bot Service Token Service to facilitate the token exchange. /// - public class RestUserTokenClient : IUserTokenClient, IDisposable + public class RestUserTokenClient : RestClientBase, IUserTokenClient, IDisposable { private readonly string _appId; - private readonly Uri _endpoint; private readonly UserTokenRestClient _userTokenClient; private readonly BotSignInRestClient _botSignInClient; private readonly ILogger _logger; private bool _disposed; - public Uri BaseUri => _endpoint; + public Uri BaseUri => Endpoint; public RestUserTokenClient(string appId, Uri endpoint, IHttpClientFactory httpClientFactory, Func> tokenProviderFunction, string namedClient = nameof(RestUserTokenClient), ILogger logger = null) + : base(endpoint, httpClientFactory, namedClient, tokenProviderFunction) { ArgumentException.ThrowIfNullOrEmpty(appId); _appId = appId; - _endpoint = endpoint; - _userTokenClient = new UserTokenRestClient(endpoint, httpClientFactory, tokenProviderFunction, namedClient); - _botSignInClient = new BotSignInRestClient(endpoint, httpClientFactory, tokenProviderFunction, namedClient); + _userTokenClient = new UserTokenRestClient(this); + _botSignInClient = new BotSignInRestClient(this); _logger = logger ?? NullLogger.Instance; } @@ -128,6 +127,10 @@ public async Task ExchangeTokenAsync(string userId, string connec _logger.LogInformation($"ExchangeAsyncAsync ConnectionName: {connectionName}"); var result = await _userTokenClient.ExchangeAsyncAsync(userId, connectionName, channelId, exchangeRequest, cancellationToken).ConfigureAwait(false); + if (result == null) + { + return null; + } if (result is ErrorResponse errorResponse) { diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs index d4a0857d..7aa3dc8e 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/ITeamsConnectorClient.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector; +using System; namespace Microsoft.Agents.Extensions.Teams.Connector { /// /// The Connector for Microsoft Teams allows your bot to perform extended operations on a Microsoft Teams channel. /// - public interface ITeamsConnectorClient : IConnectorClient + public interface ITeamsConnectorClient : IDisposable { /// /// Gets the ITeamsOperations. diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs index 580b1170..b1301d80 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs @@ -3,23 +3,26 @@ using Microsoft.Agents.Connector; using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; namespace Microsoft.Agents.Extensions.Teams.Connector { /// - /// TeamsConnectorClient REST implementation. This ConnectorClient is suitable for either ABS or SMBA. + /// TeamsConnectorClient REST implementation. /// - public class RestTeamsConnectorClient : RestConnectorClient, ITeamsConnectorClient + public class RestTeamsConnectorClient : ITeamsConnectorClient { + public RestTeamsConnectorClient(IConnectorClient connector, IRestTransport transport = null) + { + var restTransport = transport ?? connector as IRestTransport; + Teams = new RestTeamsOperations(restTransport); + } + /// public ITeamsOperations Teams { get; private set; } - public RestTeamsConnectorClient(Uri endpoint, IHttpClientFactory httpClientFactory, Func> tokenProviderFunction, string namedClient = nameof(RestTeamsConnectorClient)) : base(endpoint, httpClientFactory, tokenProviderFunction, namedClient) + public void Dispose() { - Teams = new RestTeamsOperations(httpClientFactory, namedClient, tokenProviderFunction) { Client = this }; + GC.SuppressFinalize(this); } } } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs index 4eab666c..0d62a8f3 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsOperations.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Connector; using Microsoft.Agents.Connector.RestClients; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; @@ -18,12 +19,10 @@ namespace Microsoft.Agents.Extensions.Teams.Connector /// /// TeamsOperations operations. /// - internal class RestTeamsOperations( - IHttpClientFactory httpClientFactory, - string httpClientName, - Func> tokenProviderFunction) : RestClientBase(httpClientFactory, httpClientName, tokenProviderFunction), ITeamsOperations + internal class RestTeamsOperations(IRestTransport transport) : ITeamsOperations { private static volatile RetryParams currentRetryPolicy; + private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); /// /// Gets a reference to the TeamsConnectorClient. @@ -262,7 +261,7 @@ private async Task GetResponseAsync(string operationName, string apiUrl, H var request = new HttpRequestMessage { Method = httpMethod, - RequestUri = new Uri(Client.BaseUri, apiUrl) + RequestUri = new Uri(_transport.Endpoint, apiUrl) .AppendQuery("continuationToken", continuationToken) }; request.Headers.Add("Accept", "application/json"); @@ -288,7 +287,7 @@ private async Task GetResponseAsync(string operationName, string apiUrl, H try { - using var httpClient = await GetHttpClientAsync().ConfigureAwait(false); + using var httpClient = await _transport.GetHttpClientAsync().ConfigureAwait(false); using var httpResponse = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); switch ((int)httpResponse.StatusCode) { diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs index 97198db1..77368b33 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs @@ -485,13 +485,13 @@ private static async Task GetPagedMembersAsync(IConnect private static ITeamsConnectorClient GetTeamsConnectorClient(ITurnContext turnContext) { var connectorClient = GetConnectorClient(turnContext); - if (connectorClient is ITeamsConnectorClient teamsConnector) + if (connectorClient is IRestTransport withTransport) { - return teamsConnector; + return new RestTeamsConnectorClient(connectorClient, withTransport); } else { - throw new InvalidOperationException("ITeamsConnectorClient is not available. Did you register TeamsChannelServiceClientFactory?"); + throw new InvalidOperationException("ITeamsConnectorClient is not available. Did you register IChannelServiceClientFactory?"); } } } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs deleted file mode 100644 index 7662e3cb..00000000 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/TeamsChannelServiceClientFactory.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Agents.Authentication; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.Connector; -using Microsoft.Agents.Extensions.Teams.Connector; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Microsoft.Agents.Extensions.Teams -{ - /// - /// A factory to create REST clients to interact with a Channel Service. - /// - /// - /// Connector and UserToken client factory. - /// - /// - /// - /// Thrown when an instance of is not found via . - public class TeamsChannelServiceClientFactory : IChannelServiceClientFactory - { - private readonly string _tokenServiceEndpoint; - private readonly string _tokenServiceAudience; - private readonly ILogger _logger; - private readonly IConnections _connections; - private readonly IHttpClientFactory _httpClientFactory; - - /// - /// Used to create an HttpClient with the fullname of this class - /// - /// - /// - /// - /// For testing purposes only. - public TeamsChannelServiceClientFactory( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - IConnections connections, - string tokenServiceEndpoint = AuthenticationConstants.BotFrameworkOAuthUrl, - string tokenServiceAudience = AuthenticationConstants.BotFrameworkScope, - ILogger logger = null) - { - ArgumentNullException.ThrowIfNull(configuration); - - _logger = logger ?? NullLogger.Instance; - _connections = connections ?? throw new ArgumentNullException(nameof(connections)); - _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); - - var tokenEndpoint = configuration?.GetValue($"{nameof(TeamsChannelServiceClientFactory)}:TokenServiceEndpoint"); - _tokenServiceEndpoint = string.IsNullOrWhiteSpace(tokenEndpoint) - ? tokenServiceEndpoint ?? throw new ArgumentNullException(nameof(tokenServiceEndpoint)) - : tokenEndpoint; - - var tokenAudience = configuration?.GetValue($"{nameof(TeamsChannelServiceClientFactory)}:TokenServiceAudience"); - _tokenServiceAudience = string.IsNullOrWhiteSpace(tokenAudience) - ? tokenServiceAudience ?? throw new ArgumentNullException(nameof(tokenServiceAudience)) - : tokenAudience; - } - - /// - public Task CreateConnectorClientAsync(ClaimsIdentity claimsIdentity, string serviceUrl, string audience, CancellationToken cancellationToken, IList scopes = null, bool useAnonymous = false) - { - ArgumentException.ThrowIfNullOrWhiteSpace(serviceUrl); - ArgumentException.ThrowIfNullOrWhiteSpace(audience); - - // Intentionally create the TeamsConnectorClient since it supports the same operations as for ABS plus the Teams operations. - return Task.FromResult(new RestTeamsConnectorClient( - new Uri(serviceUrl), - _httpClientFactory, - useAnonymous ? null : () => - { - var tokenAccess = _connections.GetTokenProvider(claimsIdentity, serviceUrl) - ?? throw new InvalidOperationException($"An instance of IAccessTokenProvider not found for {BotClaims.GetAppId(claimsIdentity)}:{serviceUrl}"); - return tokenAccess.GetAccessTokenAsync(audience, scopes); - }, - typeof(TeamsChannelServiceClientFactory).FullName)); - } - - /// - public Task CreateUserTokenClientAsync(ClaimsIdentity claimsIdentity, CancellationToken cancellationToken, bool useAnonymous = false) - { - ArgumentNullException.ThrowIfNull(claimsIdentity); - - var appId = BotClaims.GetAppId(claimsIdentity) ?? Guid.Empty.ToString(); - - return Task.FromResult(new RestUserTokenClient( - appId, - new Uri(_tokenServiceEndpoint), - _httpClientFactory, - useAnonymous ? null : () => - { - var tokenAccess = _connections.GetTokenProvider(claimsIdentity, _tokenServiceEndpoint) - ?? throw new InvalidOperationException($"An instance of IAccessTokenProvider not found for {BotClaims.GetAppId(claimsIdentity)}:{_tokenServiceEndpoint}"); - return tokenAccess.GetAccessTokenAsync(_tokenServiceAudience, null); - }, - typeof(TeamsChannelServiceClientFactory).FullName, - _logger)); - } - } -} diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 3538e27d..054d6f9d 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -44,26 +44,18 @@ public static void AddCloudAdapter(this IServiceCollection services) where T public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) where TBot : class, IBot { - return AddBot(builder); + return AddBot(builder); } public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) where TBot : class, IBot where TAdapter : CloudAdapter - { - return AddBot(builder); - } - - public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) - where TBot : class, IBot - where TAdapter : CloudAdapter - where TClientFactory : class, IChannelServiceClientFactory { // Add Connections object to access configured token connections. builder.Services.AddSingleton(); // Add factory for ConnectorClient and UserTokenClient creation - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Add IStorage for turn state persistence builder.Services.AddSingleton(); diff --git a/src/samples/Teams/AdaptiveCardActions/Program.cs b/src/samples/Teams/AdaptiveCardActions/Program.cs index d42d61b9..53b3280c 100644 --- a/src/samples/Teams/AdaptiveCardActions/Program.cs +++ b/src/samples/Teams/AdaptiveCardActions/Program.cs @@ -4,7 +4,6 @@ using AdaptiveCardActions.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -22,7 +21,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/ConversationBot/Program.cs b/src/samples/Teams/ConversationBot/Program.cs index 111ee673..13f754fe 100644 --- a/src/samples/Teams/ConversationBot/Program.cs +++ b/src/samples/Teams/ConversationBot/Program.cs @@ -4,7 +4,6 @@ using ConversationBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -18,7 +17,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/LinkUnfurling/Program.cs b/src/samples/Teams/LinkUnfurling/Program.cs index e9eede15..7bbc595b 100644 --- a/src/samples/Teams/LinkUnfurling/Program.cs +++ b/src/samples/Teams/LinkUnfurling/Program.cs @@ -4,7 +4,6 @@ using LinkUnfurling.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -18,7 +17,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/Meetings-Notification/Program.cs b/src/samples/Teams/Meetings-Notification/Program.cs index 07f60950..6ab1c660 100644 --- a/src/samples/Teams/Meetings-Notification/Program.cs +++ b/src/samples/Teams/Meetings-Notification/Program.cs @@ -4,7 +4,6 @@ using InMeetingNotificationsBot.Bots; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -20,7 +19,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); app.MapRazorPages(); diff --git a/src/samples/Teams/MessagingExtensionsSearch/Program.cs b/src/samples/Teams/MessagingExtensionsSearch/Program.cs index 3cd899f7..5de2cd0a 100644 --- a/src/samples/Teams/MessagingExtensionsSearch/Program.cs +++ b/src/samples/Teams/MessagingExtensionsSearch/Program.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Agents.Hosting.AspNetCore; using MessagingExtensionsSearch.Bots; -using Microsoft.Agents.Extensions.Teams; var builder = WebApplication.CreateBuilder(args); @@ -22,7 +21,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/TaskModule/Program.cs b/src/samples/Teams/TaskModule/Program.cs index e2f68cbc..3835a1a4 100644 --- a/src/samples/Teams/TaskModule/Program.cs +++ b/src/samples/Teams/TaskModule/Program.cs @@ -3,7 +3,6 @@ using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; -using Microsoft.Agents.Extensions.Teams; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -21,7 +20,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); var app = builder.Build(); diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs index ddd956e1..75da4980 100644 --- a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs +++ b/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs @@ -12,7 +12,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Agents.Extensions.Teams.Compat; -using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.Compat; @@ -29,7 +28,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot, TeamsSSOAdapter, TeamsChannelServiceClientFactory>(); +builder.AddBot, TeamsSSOAdapter>(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs index d8153812..532c6518 100644 --- a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs +++ b/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PeoplePicker.Bots; -using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); @@ -24,7 +23,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs index baf91f9c..56f4303c 100644 --- a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs +++ b/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ReceiveMessagesWithRSC.Bots; -using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); @@ -24,7 +23,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot(); +builder.AddBot(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/samples/Teams/bot-tag-mention/Program.cs b/src/samples/Teams/bot-tag-mention/Program.cs index 785a31be..93a146d4 100644 --- a/src/samples/Teams/bot-tag-mention/Program.cs +++ b/src/samples/Teams/bot-tag-mention/Program.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Agents.Extensions.Teams.Compat; -using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.State; @@ -27,7 +26,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot, CloudAdapter, TeamsChannelServiceClientFactory>(); +builder.AddBot>(); builder.Services.AddSingleton((sp) => { diff --git a/src/samples/Teams/bot-teams-authentication/Program.cs b/src/samples/Teams/bot-teams-authentication/Program.cs index 200fa610..a9b83536 100644 --- a/src/samples/Teams/bot-teams-authentication/Program.cs +++ b/src/samples/Teams/bot-teams-authentication/Program.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Hosting; using TeamsAuth.Bots; using TeamsAuth.Dialogs; -using Microsoft.Agents.Extensions.Teams; using Microsoft.Agents.BotBuilder.State; var builder = WebApplication.CreateBuilder(args); @@ -21,7 +20,7 @@ builder.Services.AddBotAspNetAuthentication(builder.Configuration); // Add basic bot functionality -builder.AddBot, CloudAdapter, TeamsChannelServiceClientFactory>(); +builder.AddBot>(); // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) builder.Services.AddSingleton(); diff --git a/src/tests/Microsoft.Agents.Connector.Tests/AttachmentsRestClientTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/AttachmentsRestClientTests.cs index 3321621d..a042aaeb 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/AttachmentsRestClientTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/AttachmentsRestClientTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Connector; using Microsoft.Agents.Connector.RestClients; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; @@ -49,21 +50,9 @@ public void Constructor_ShouldInstantiateCorrectly() } [Fact] - public void Constructor_ShouldThrowOnNullFactory() + public void Constructor_ShouldThrowOnNullTransport() { - Assert.Throws(() => new AttachmentsRestClient(UriEndpoint, null, null)); - } - - [Fact] - public void Constructor_ShouldThrowOnNullEndpoint() - { - Assert.Throws(() => new AttachmentsRestClient(null, null, null)); - } - - [Fact] - public void Constructor_ShouldNotThrowOnNullTokenProvider() - { - _ = new AttachmentsRestClient(UriEndpoint, new Mock().Object, null); + Assert.Throws(() => new AttachmentsRestClient(null)); } [Fact] @@ -232,11 +221,13 @@ public async Task GetAttachmentAsync_ShouldThrowWithoutErrorBody() private AttachmentsRestClient UseAttachment() { - var httpFactory = new Mock(); - httpFactory.Setup(a => a.CreateClient(It.IsAny())) - .Returns(MockHttpClient.Object); + var transport = new Mock(); + transport.Setup(x => x.Endpoint) + .Returns(UriEndpoint); + transport.Setup(a => a.GetHttpClientAsync()) + .Returns(Task.FromResult(MockHttpClient.Object)); - return new AttachmentsRestClient(UriEndpoint, httpFactory.Object, null); + return new AttachmentsRestClient(transport.Object); } } } diff --git a/src/tests/Microsoft.Agents.Connector.Tests/BotSignInRestClientTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/BotSignInRestClientTests.cs index 5257d388..d6b8b880 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/BotSignInRestClientTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/BotSignInRestClientTests.cs @@ -31,15 +31,9 @@ public void Constructor_ShouldInstantiateCorrectly() } [Fact] - public void Constructor_ShouldThrowOnNullEndpoint() + public void Constructor_ShouldThrowOnNullTransport() { - Assert.Throws(() => new BotSignInRestClient(null, null, null)); - } - - [Fact] - public void Constructor_ShouldThrowOnNullFactory() - { - Assert.Throws(() => new BotSignInRestClient(Endpoint, null, null)); + Assert.Throws(() => new BotSignInRestClient(null)); } [Fact] @@ -124,11 +118,13 @@ public async Task GetSignInResourceAsync_ShouldReturnSignInResource() private static BotSignInRestClient UseClient() { - var httpFactory = new Mock(); - httpFactory.Setup(a => a.CreateClient(It.IsAny())) - .Returns(MockHttpClient.Object); + var transport = new Mock(); + transport.Setup(x => x.Endpoint) + .Returns(Endpoint); + transport.Setup(a => a.GetHttpClientAsync()) + .Returns(Task.FromResult(MockHttpClient.Object)); - return new BotSignInRestClient(Endpoint, httpFactory.Object, () => Task.FromResult("test")); + return new BotSignInRestClient(transport.Object); } } } diff --git a/src/tests/Microsoft.Agents.Connector.Tests/ConversationsRestClientTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/ConversationsRestClientTests.cs index 29c4ad03..0360ba1a 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/ConversationsRestClientTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/ConversationsRestClientTests.cs @@ -64,21 +64,9 @@ public void Constructor_ShouldInstantiateCorrectly() } [Fact] - public void Constructor_ShouldThrowOnNullFactory() + public void Constructor_ShouldThrowOnNullTransport() { - Assert.Throws(() => new ConversationsRestClient(UriEndpoint, null, null)); - } - - [Fact] - public void Constructor_ShouldNotThrowOnNullTokenProvider() - { - _ = new AttachmentsRestClient(UriEndpoint, new Mock().Object, null); - } - - [Fact] - public void Constructor_ShouldThrowOnNullEndpoint() - { - Assert.Throws(() => new ConversationsRestClient(null, null, null)); + Assert.Throws(() => new ConversationsRestClient(null)); } [Fact] @@ -1055,11 +1043,13 @@ public async Task UploadAttachmentAsync_ShouldThrowWithoutErrorBody() private ConversationsRestClient UseConversation() { - var httpFactory = new Mock(); - httpFactory.Setup(a => a.CreateClient(It.IsAny())) - .Returns(MockHttpClient.Object); + var transport = new Mock(); + transport.Setup(x => x.Endpoint) + .Returns(UriEndpoint); + transport.Setup(a => a.GetHttpClientAsync()) + .Returns(Task.FromResult(MockHttpClient.Object)); - return new ConversationsRestClient(UriEndpoint, httpFactory.Object, () => Task.FromResult("test")); + return new ConversationsRestClient(transport.Object); } } } diff --git a/src/tests/Microsoft.Agents.Connector.Tests/UserTokenRestClientTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/UserTokenRestClientTests.cs index a2720992..4ede749e 100644 --- a/src/tests/Microsoft.Agents.Connector.Tests/UserTokenRestClientTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/UserTokenRestClientTests.cs @@ -24,7 +24,7 @@ public class UserTokenRestClientTests private const string ChannelId = "channel-id"; private const string Code = "code"; private const string Include = "include"; - private readonly AadResourceUrls AadResourceUrls = new() { ResourceUrls = ["resource-url"] }; + private readonly string[] AadResourceUrls = ["resource-url"]; private readonly TokenExchangeRequest TokenExchangeRequest = new(); private static readonly Mock MockHttpClient = new(); @@ -38,33 +38,33 @@ public void Constructor_ShouldInstantiateCorrectly() [Fact] public void Constructor_ShouldThrowOnNullFactory() { - Assert.Throws(() => new UserTokenRestClient(Endpoint, null, null, null)); + Assert.Throws(() => new RestUserTokenClient("appId", Endpoint, null, null, null)); } [Fact] public void Constructor_ShouldThrowOnNullEndpoint() { - Assert.Throws(() => new UserTokenRestClient(null, null, null, null)); + Assert.Throws(() => new RestUserTokenClient("appId", null, null, null, null, null)); } [Fact] public void Constructor_ShouldThrowOnNullTokenProvider() { - Assert.Throws(() => new UserTokenRestClient(Endpoint, new Mock().Object, null, null)); + Assert.Throws(() => new RestUserTokenClient("appId", Endpoint, new Mock().Object, null, null)); } [Fact] public async Task GetTokenAsync_ShouldThrowOnNullUserId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetTokenAsync(null, ConnectionName, ChannelId)); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(null, ConnectionName, ChannelId, "code", CancellationToken.None)); } [Fact] public async Task GetTokenAsync_ShouldThrowOnNullConnectionName() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetTokenAsync(UserId, null, ChannelId)); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(UserId, null, ChannelId, "code", CancellationToken.None)); } [Fact] @@ -84,7 +84,7 @@ public async Task GetTokenAsync_ShouldReturnToken() var client = UseClient(); - var result = await client.GetTokenAsync(UserId, ConnectionName, ChannelId, Code); + var result = await client.GetUserTokenAsync(UserId, ConnectionName, ChannelId, Code, CancellationToken.None); Assert.Equal(tokenResponse.Token, result.Token); } @@ -96,7 +96,7 @@ public async Task GetTokenAsync_ShouldReturnNullOnNotFound() var client = UseClient(); - Assert.Null(await client.GetTokenAsync(UserId, ConnectionName, ChannelId, Code)); + Assert.Null(await client.GetUserTokenAsync(UserId, ConnectionName, ChannelId, Code, CancellationToken.None)); } [Fact] @@ -106,14 +106,14 @@ public async Task GetTokenAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetTokenAsync(UserId, ConnectionName, ChannelId, Code)); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(UserId, ConnectionName, ChannelId, Code, CancellationToken.None)); } [Fact] public async Task SignOutAsync_ShouldThrowOnNullUserId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.SignOutAsync(null, ConnectionName, ChannelId)); + await Assert.ThrowsAsync(() => client.SignOutUserAsync(null, ConnectionName, ChannelId, CancellationToken.None)); } [Fact] @@ -133,7 +133,8 @@ public async Task SignOutAsync_ShouldReturnContent() var client = UseClient(); - Assert.NotNull(await client.SignOutAsync(UserId, ConnectionName, ChannelId)); + // will throw for test failure + await client.SignOutUserAsync(UserId, ConnectionName, ChannelId, CancellationToken.None); } [Fact] @@ -143,7 +144,8 @@ public async Task SignOutAsync_ShouldReturnNullOnNoContent() var client = UseClient(); - Assert.Null(await client.SignOutAsync(UserId, ConnectionName, ChannelId)); + // no error should happen + await client.SignOutUserAsync(UserId, ConnectionName, ChannelId, CancellationToken.None); } [Fact] @@ -153,14 +155,14 @@ public async Task SignOutAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.SignOutAsync(UserId, ConnectionName, ChannelId)); + await Assert.ThrowsAsync(async () => await client.SignOutUserAsync(UserId, ConnectionName, ChannelId, CancellationToken.None)); } [Fact] public async Task GetTokenStatusAsync_ShouldThrowOnNullUserId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(null)); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(null, null, null, CancellationToken.None)); } [Fact] @@ -182,7 +184,7 @@ public async Task GetTokenStatusAsync_ShouldReturnTokenStatus() var client = UseClient(); - var status = await client.GetTokenStatusAsync(UserId, ConnectionName, ChannelId); + var status = await client.GetTokenStatusAsync(UserId, ConnectionName, ChannelId, CancellationToken.None); Assert.Single(status); Assert.True(status[0].HasToken); @@ -195,21 +197,21 @@ public async Task GetTokenStatusAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, ChannelId, Include)); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, ChannelId, Include, CancellationToken.None)); } [Fact] public async Task GetAadTokensAsync_ShouldThrowOnNullUserId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetAadTokensAsync(null, ConnectionName, AadResourceUrls)); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(null, ConnectionName, AadResourceUrls, "channelId", CancellationToken.None)); } [Fact] public async Task GetAadTokensAsync_ShouldThrowOnNullConnectionName() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, null, AadResourceUrls)); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, null, AadResourceUrls, "channelId", CancellationToken.None)); } [Fact] @@ -235,7 +237,7 @@ public async Task GetAadTokensAsync_ShouldReturnTokens() var client = UseClient(); - var result = await client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId); + var result = await client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId, CancellationToken.None); Assert.Equal(2, result.Count); Assert.Equal(tokens["firstToken"].Token, result["firstToken"].Token); @@ -249,35 +251,35 @@ public async Task GetAadTokensAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId)); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId, CancellationToken.None)); } [Fact] public async Task ExchangeAsync_ShouldThrowOnNullUserId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(null, ConnectionName, ChannelId, TokenExchangeRequest)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(null, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] public async Task ExchangeAsync_ShouldThrowOnNullConnectionName() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, null, ChannelId, TokenExchangeRequest)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, null, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] public async Task ExchangeAsync_ShouldThrowOnNullChannelId() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, null, TokenExchangeRequest)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, ConnectionName, null, TokenExchangeRequest, CancellationToken.None)); } [Fact] public async Task ExchangeAsync_ShouldThrowOnNullExchangeRequest() { var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, null)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, null, CancellationToken.None)); } [Fact] @@ -297,7 +299,7 @@ public async Task ExchangeAsync_ShouldReturnContent() var client = UseClient(); - Assert.NotNull(await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest)); + Assert.NotNull(await client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] @@ -307,7 +309,7 @@ public async Task ExchangeAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] @@ -328,7 +330,7 @@ public async Task ExchangeTokenAsync_ShouldReturnContent() var client = UseClient(); - var response = await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest); + var response = await client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None); Assert.NotNull(response); Assert.Equal(ConnectionName, ((TokenResponse)response).ConnectionName); Assert.Equal("test-token", ((TokenResponse)response).Token); @@ -341,7 +343,7 @@ public async Task ExchangeTokenAsync_ShouldThrowOnError() var client = UseClient(); - await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest)); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] @@ -361,9 +363,7 @@ public async Task ExchangeTokenAsync_ErrorResponseFor400() var client = UseClient(); - var response = await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest); - Assert.IsAssignableFrom(response); - Assert.Equal("Error Message", ((ErrorResponse)response).Error.Message); + await Assert.ThrowsAsync(async () => await client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); } [Fact] @@ -383,7 +383,7 @@ public async Task ExchangeTokenAsync_WithContent404() var client = UseClient(); - var response = await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest); + var response = await client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None); Assert.IsAssignableFrom(response); Assert.Equal(ConnectionName, ((TokenResponse)response).ConnectionName); } @@ -395,17 +395,17 @@ public async Task ExchangeTokenAsync_WithoutContent404() var client = UseClient(); - var response = await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest); + var response = await client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None); Assert.Null(response); } - private static UserTokenRestClient UseClient() + private static RestUserTokenClient UseClient() { var httpFactory = new Mock(); httpFactory.Setup(a => a.CreateClient(It.IsAny())) .Returns(MockHttpClient.Object); - return new UserTokenRestClient(Endpoint, httpFactory.Object, () => Task.FromResult("test")); + return new RestUserTokenClient("appId", Endpoint, httpFactory.Object, () => Task.FromResult("test")); } } } diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs index e84bf3c5..2d8a0980 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/Connector/RestTeamsOperationsTests.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Microsoft.Agents.Connector; namespace Microsoft.Agents.Extensions.Teams.Tests { @@ -47,9 +48,9 @@ public void Constructor_ShouldInstantiateCorrectly() } [Fact] - public void Constructor_ShouldThrowOnNullClientFactory() + public void Constructor_ShouldThrowOnNullConnector() { - Assert.Throws(() => new RestTeamsOperations(null, null, null)); + Assert.Throws(() => new RestTeamsOperations(null)); } [Fact] @@ -534,14 +535,13 @@ public async Task GetResponseAsync_ShouldThrowThrottleExceptionsAfterRetry() private RestTeamsOperations UseTeamsOperations() { - var httpFactory = new Mock(); - httpFactory.Setup(a => a.CreateClient(It.IsAny())) - .Returns(MockHttpClient.Object); + var transport = new Mock(); + transport.Setup(x => x.Endpoint) + .Returns(new Uri(ServiceUrl)); + transport.Setup(a => a.GetHttpClientAsync()) + .Returns(Task.FromResult(MockHttpClient.Object)); - return new RestTeamsOperations(httpFactory.Object, nameof(RestTeamsConnectorClient), null) - { - Client = new RestTeamsConnectorClient(new Uri("http://teams.com"), httpFactory.Object, null) - }; + return new RestTeamsOperations(transport.Object); } } } From 02afc13188efa541bcf5abcf92695ec58cacd408 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 13 Feb 2025 09:00:02 -0600 Subject: [PATCH 40/60] Added TeamsAttachmentDownloader --- .../App/Application.cs | 5 +- .../App/IInputFileDownloader.cs | 2 +- .../App/TypingTimer.cs | 2 +- .../State/TempState.cs | 4 +- .../App/TeamsAttachmentDownloader.cs | 163 ++++++++++++++++++ .../Connector/RestTeamsConnectorClient.cs | 1 + 6 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index c00a9eea..dd10bf87 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -633,8 +634,8 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc { foreach (IInputFileDownloader downloader in fileDownloaders) { - List files = await downloader.DownloadFilesAsync(turnContext, turnState, cancellationToken).ConfigureAwait(false); - turnState.Temp.InputFiles.AddRange(files); + var files = await downloader.DownloadFilesAsync(turnContext, turnState, cancellationToken).ConfigureAwait(false); + turnState.Temp.InputFiles = [.. turnState.Temp.InputFiles, .. files]; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs index a7641dba..ef45f494 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IInputFileDownloader.cs @@ -20,6 +20,6 @@ public interface IInputFileDownloader /// The turn state. /// The cancellation token. /// A list of input files - public Task> DownloadFilesAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken = default); + public Task> DownloadFilesAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken = default); } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index 2c16f7ba..be63e2a2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -66,7 +66,7 @@ public bool Start(ITurnContext turnContext) _timer = new Timer(SendTypingActivity, turnContext, Timeout.Infinite, Timeout.Infinite); // Fire first time - _timer.Change(0, Timeout.Infinite); + _timer.Change(_initialDelay, Timeout.Infinite); return true; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs index 57a2fb88..33cc9e25 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs @@ -24,9 +24,9 @@ public class TempState : IBotState /// /// Downloaded files included in the Activity /// - public List InputFiles + public IList InputFiles { - get => GetValue>(InputFilesKey)!; + get => GetValue>(InputFilesKey, () => []); set => SetValue(InputFilesKey, value); } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs new file mode 100644 index 00000000..c29470c0 --- /dev/null +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.Extensions.Teams.App +{ + /// + /// Downloads attachments from Teams using the Bot access token. + /// + public class TeamsAttachmentDownloader : IInputFileDownloader + { + private readonly TeamsAttachmentDownloaderOptions _options; + private readonly HttpClient _httpClient; + private readonly IAccessTokenProvider _accessTokenProvider; + + + /// + /// Creates the TeamsAttachmentDownloader + /// + /// The options + /// + /// + /// + public TeamsAttachmentDownloader(TeamsAttachmentDownloaderOptions options, IConnections connections, IHttpClientFactory httpClientFactory) + { + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(connections); + ArgumentNullException.ThrowIfNull(httpClientFactory); + + _options = options; + if (string.IsNullOrEmpty(_options.TokenProviderName)) + { + throw new ArgumentException("TeamsAttachmentDownloader.TokenProviderName is empty."); + } + + _accessTokenProvider = connections.GetConnection(_options.TokenProviderName); + if (_accessTokenProvider == null) + { + throw new ArgumentException("TeamsAttachmentDownloader.TokenProviderName not found."); + } + + _httpClient = httpClientFactory.CreateClient(nameof(TeamsAttachmentDownloader)); + } + + /// + public async Task> DownloadFilesAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + if (!string.Equals(Channels.Msteams, turnContext.Activity.ChannelId, StringComparison.OrdinalIgnoreCase)) + { + return []; + } + + // Filter out HTML attachments + IEnumerable? attachments = turnContext.Activity.Attachments?.Where((a) => !a.ContentType.StartsWith("text/html")); + if (attachments == null || !attachments.Any()) + { + return new List(); + } + + string accessToken = ""; + + // If authentication is enabled, get access token + if (!_options.UseAnonymous) + { + accessToken = await _accessTokenProvider.GetAccessTokenAsync(BotClaims.GetTokenAudience(turnContext.Identity), _options.Scopes).ConfigureAwait(false); + } + + List files = new(); + + foreach (Attachment attachment in attachments) + { + InputFile? file = await _DownloadFile(attachment, accessToken); + if (file != null) + { + files.Add(file); + } + } + + return files; + } + + + private async Task _DownloadFile(Attachment attachment, string accessToken) + { + string? name = attachment.Name; + + if (attachment.ContentUrl != null && (attachment.ContentUrl.StartsWith("https://") || attachment.ContentUrl.StartsWith("http://localhost"))) + { + // Get downloadable content link + var contentProperties = ProtocolJsonSerializer.ToJsonElements(attachment.Content); + if (contentProperties == null || !contentProperties.ContainsKey("downloadUrl")) + { + return null; + } + + string? downloadUrl = contentProperties["downloadUrl"].ToString(); + if (downloadUrl == null) + { + downloadUrl = attachment.ContentUrl; + } + + using (HttpRequestMessage request = new(HttpMethod.Get, downloadUrl)) + { + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + + HttpResponseMessage response = await _httpClient.SendAsync(request).ConfigureAwait(false); + + // Failed to download file + if (!response.IsSuccessStatusCode) + { + return null; + } + + // Convert to a buffer + byte[] content = await response.Content.ReadAsByteArrayAsync(); + + // Fixup content type + string contentType = response.Content.Headers.ContentType.MediaType; + if (contentType.StartsWith("image/")) + { + contentType = "image/png"; + } + + return new InputFile(new BinaryData(content), contentType) + { + ContentUrl = attachment.ContentUrl, + Filename = name + }; + } + } + else + { + return new InputFile(new BinaryData(attachment.Content), attachment.ContentType) + { + ContentUrl = attachment.ContentUrl, + Filename = name + }; + } + } + } + + /// + /// The TeamsAttachmentDownloader options + /// + public class TeamsAttachmentDownloaderOptions + { + public string TokenProviderName { get; set; } + public bool UseAnonymous { get; set; } = false; + public IList Scopes { get; set; } = null; + } +} diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs index b1301d80..5f764131 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/RestTeamsConnectorClient.cs @@ -14,6 +14,7 @@ public class RestTeamsConnectorClient : ITeamsConnectorClient public RestTeamsConnectorClient(IConnectorClient connector, IRestTransport transport = null) { var restTransport = transport ?? connector as IRestTransport; + ArgumentNullException.ThrowIfNull(nameof(restTransport)); Teams = new RestTeamsOperations(restTransport); } From 1c178f1c5b10536376d29ac1b0e990b707a9abea Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 13 Feb 2025 10:03:49 -0600 Subject: [PATCH 41/60] Added Extensions back to solution lost during merge --- src/Microsoft.Agents.SDK.sln | 17 +++++++++++++++++ .../App/Application.cs | 1 - .../messaging.echoBot/EchoBot.csproj | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 0376beaf..601be43a 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -140,6 +140,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{47E36E7C-7160-4ED3-A43A-D53D27361CD4}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{927E4F54-6FBC-4390-BF64-BF3C1874C1AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.SharePoint", "libraries\Extensions\Microsoft.Agents.Extensions.SharePoint\Microsoft.Agents.Extensions.SharePoint.csproj", "{A8EDAEA3-4057-4862-BC92-F9CFA645246A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.Teams", "libraries\Extensions\Microsoft.Agents.Extensions.Teams\Microsoft.Agents.Extensions.Teams.csproj", "{348A61E5-E16A-47ED-B621-F2C61E1E316D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -362,6 +368,14 @@ Global {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Debug|Any CPU.Build.0 = Debug|Any CPU {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Release|Any CPU.ActiveCfg = Release|Any CPU {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Release|Any CPU.Build.0 = Release|Any CPU + {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Release|Any CPU.Build.0 = Release|Any CPU + {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -431,6 +445,9 @@ Global {8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {851DB8B0-CD62-414F-B370-EC3680563B6E} = {674A812C-7287-4883-97F9-697D83750648} {47E36E7C-7160-4ED3-A43A-D53D27361CD4} = {851DB8B0-CD62-414F-B370-EC3680563B6E} + {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} + {A8EDAEA3-4057-4862-BC92-F9CFA645246A} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} + {348A61E5-E16A-47ED-B621-F2C61E1E316D} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index dd10bf87..4684ba01 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; diff --git a/src/samples/Application/messaging.echoBot/EchoBot.csproj b/src/samples/Application/messaging.echoBot/EchoBot.csproj index c520da06..f7a76b85 100644 --- a/src/samples/Application/messaging.echoBot/EchoBot.csproj +++ b/src/samples/Application/messaging.echoBot/EchoBot.csproj @@ -21,4 +21,8 @@ + + + + From 846d2b839e3845433bfe8e9a48a9bfbf255981d0 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 13 Feb 2025 10:07:01 -0600 Subject: [PATCH 42/60] Minor error message changes --- .../Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs index 77368b33..448a89d9 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/Connector/TeamsInfo.cs @@ -450,7 +450,7 @@ private static async Task> GetMembersAsync(ICon private static IConnectorClient GetConnectorClient(ITurnContext turnContext) { - return turnContext.Services.Get() ?? throw new InvalidOperationException("This method requires a connector client."); + return turnContext.Services.Get() ?? throw new InvalidOperationException("IConnectorClient is not available. Was IChannelServiceClientFactory registered?"); } private static async Task GetMemberAsync(IConnectorClient connectorClient, string userId, string conversationId, CancellationToken cancellationToken) @@ -491,7 +491,7 @@ private static ITeamsConnectorClient GetTeamsConnectorClient(ITurnContext turnCo } else { - throw new InvalidOperationException("ITeamsConnectorClient is not available. Did you register IChannelServiceClientFactory?"); + throw new InvalidOperationException("ITeamsConnectorClient is not available. The registered ConnectorClient does not support IRestTransport"); } } } From 25c743d41f5da316b28eaf65c029d7093fe99e59 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 13 Feb 2025 14:21:22 -0600 Subject: [PATCH 43/60] Fixed TypingTimer race --- .../App/TypingTimer.cs | 28 ++++++++++++++++--- .../ChannelServiceAdapterBase.cs | 2 -- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index be63e2a2..97e7c0bb 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -20,6 +20,9 @@ internal class TypingTimer : IDisposable /// private readonly int _interval; + // For synchronizing SendActivity and Typing to prevent race + private static Mutex _send; + /// /// Initial delay before first typing is sent. /// @@ -35,7 +38,7 @@ internal class TypingTimer : IDisposable /// /// The interval in milliseconds to send "typing" activity. /// Initial delay - public TypingTimer(int interval = 1000, int initialDelay = 500) + public TypingTimer(int interval = 5000, int initialDelay = 500) { _interval = interval; _initialDelay = initialDelay; @@ -62,6 +65,8 @@ public bool Start(ITurnContext turnContext) // Stop timer when message activities are sent turnContext.OnSendActivities(StopTimerWhenSendMessageActivityHandlerAsync); + _send = new Mutex(false); + // Start periodically send "typing" activity _timer = new Timer(SendTypingActivity, turnContext, Timeout.Infinite, Timeout.Infinite); @@ -112,10 +117,14 @@ private async void SendTypingActivity(object state) try { - await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing, RelatesTo = turnContext.Activity.RelatesTo, Text = "TYPING" }); - if (IsRunning()) + _send.WaitOne(); + if (_timer != null) { - _timer?.Change(_interval, Timeout.Infinite); + await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing, RelatesTo = turnContext.Activity.RelatesTo, Text = "TYPING" }); + if (IsRunning()) + { + _timer?.Change(_interval, Timeout.Infinite); + } } } catch (Exception e) when (e is ObjectDisposedException || e is TaskCanceledException || e is NullReferenceException) @@ -125,6 +134,10 @@ private async void SendTypingActivity(object state) // error but lets make sure our states cleaned up a bit. Dispose(); } + finally + { + _send.ReleaseMutex(); + } } private Task StopTimerWhenSendMessageActivityHandlerAsync(ITurnContext turnContext, List activities, Func> next) @@ -135,7 +148,14 @@ private Task StopTimerWhenSendMessageActivityHandlerAsync(IT { if (activity.Type == ActivityTypes.Message) { + // This will block ITurnContext.SendActivity until the typing timer is done. + _send.WaitOne(); + + // Stop timer Dispose(); + + // Release, which could free up timer SendTypingActivity (if it was blocked). + _send.ReleaseMutex(); break; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index 8cd289f7..69e2707f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -6,11 +6,9 @@ using System.Globalization; using System.Net; using System.Security.Claims; -using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Authentication; -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Connector; using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; From 42f99bd567c6e6fb8ea79fe8283ca1ed09d164c7 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 13 Feb 2025 17:12:39 -0600 Subject: [PATCH 44/60] Added AddBot Func variation --- .../AspNetCore/ServiceCollectionExtensions.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 054d6f9d..9e1661ca 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -41,6 +41,15 @@ public static void AddCloudAdapter(this IServiceCollection services) where T services.AddSingleton(sp => sp.GetService()); } + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory) + { + AddCore(builder); + + builder.Services.AddTransient(implementationFactory); + + return builder; + } + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) where TBot : class, IBot { @@ -51,17 +60,7 @@ public static IHostApplicationBuilder AddBot(this IHostApplicati where TBot : class, IBot where TAdapter : CloudAdapter { - // Add Connections object to access configured token connections. - builder.Services.AddSingleton(); - - // Add factory for ConnectorClient and UserTokenClient creation - builder.Services.AddSingleton(); - - // Add IStorage for turn state persistence - builder.Services.AddSingleton(); - - // Add the ChannelAdapter, this is the default adapter that works with Azure Bot Service and Activity Protocol. - AddCloudAdapter(builder.Services); + AddCore(builder); // Add the Bot, this is the primary worker for the bot. builder.Services.AddTransient(); @@ -104,6 +103,22 @@ public static IHostApplicationBuilder AddChannelHost(this IHostApplica return builder; } + private static void AddCore(this IHostApplicationBuilder builder) + where TAdapter : CloudAdapter + { + // Add Connections object to access configured token connections. + builder.Services.AddSingleton(); + + // Add factory for ConnectorClient and UserTokenClient creation + builder.Services.AddSingleton(); + + // Add IStorage for turn state persistence + builder.Services.AddSingleton(); + + // Add the ChannelAdapter, this is the default adapter that works with Azure Bot Service and Activity Protocol. + AddCloudAdapter(builder.Services); + } + private static void AddAsyncCloudAdapterSupport(this IServiceCollection services) { // Activity specific BackgroundService for processing authenticated activities. From 3cb449e57e916154a6b21682fc733ed9b6772a85 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 18 Feb 2025 10:16:24 -0600 Subject: [PATCH 45/60] Added Application based Msg Exensions Search Command --- src/Microsoft.Agents.SDK.sln | 9 + .../.gitignore | 25 +++ .../ActivityHandlers.cs | 118 ++++++++++ .../AspNetExtensions.cs | 201 ++++++++++++++++++ .../Config.cs | 8 + .../Controllers/BotController.cs | 23 ++ .../Model/CardPackage.cs | 43 ++++ .../Model/Package.cs | 29 +++ .../Program.cs | 50 +++++ .../README.md | 31 +++ .../Resources/PackageCard.json | 55 +++++ .../SearchCommand.csproj | 40 ++++ .../SearchCommand.sln | 27 +++ .../appPackage/color.png | Bin 0 -> 1066 bytes .../appPackage/manifest.json | 71 +++++++ .../appPackage/outline.png | Bin 0 -> 249 bytes .../appsettings.json | 38 ++++ .../assets/card.png | Bin 0 -> 76643 bytes .../assets/search.png | Bin 0 -> 61027 bytes .../env/.env.dev | 18 ++ .../infra/azure.bicep | 74 +++++++ .../infra/azure.parameters.json | 21 ++ .../infra/botRegistration/README.md | 1 + .../infra/botRegistration/azurebot.bicep | 37 ++++ .../teamsapp.local.yml | 91 ++++++++ .../teamsapp.yml | 83 ++++++++ 26 files changed, 1093 insertions(+) create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/.gitignore create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/AspNetExtensions.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Config.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Program.cs create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/README.md create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/manifest.json create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/outline.png create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appsettings.json create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/assets/card.png create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/assets/search.png create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml create mode 100644 src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 601be43a..a739cdd2 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -146,6 +146,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.Teams", "libraries\Extensions\Microsoft.Agents.Extensions.Teams\Microsoft.Agents.Extensions.Teams.csproj", "{348A61E5-E16A-47ED-B621-F2C61E1E316D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchCommand", "samples\Application\messageExtensions.a.searchCommand\SearchCommand.csproj", "{AC149C90-3191-4995-B6F5-7EA35F311EAB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -376,6 +378,12 @@ Global {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Debug|Any CPU.Build.0 = Debug|Any CPU {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.ActiveCfg = Release|Any CPU {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.Build.0 = Release|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Build.0 = Release|Any CPU + {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -448,6 +456,7 @@ Global {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {A8EDAEA3-4057-4862-BC92-F9CFA645246A} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} {348A61E5-E16A-47ED-B621-F2C61E1E316D} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} + {AC149C90-3191-4995-B6F5-7EA35F311EAB} = {851DB8B0-CD62-414F-B370-EC3680563B6E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/.gitignore b/src/samples/Application/messageExtensions.a.searchCommand/.gitignore new file mode 100644 index 00000000..ee83f445 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/.gitignore @@ -0,0 +1,25 @@ +# VS +.vs + +# User-specific files +*.user + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Teams Toolkit +env/.env.*.user +appPackage/build +.deployment +# Teams Toolkit Local Development +appsettings.Development.json +env/.env.local diff --git a/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs b/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs new file mode 100644 index 00000000..f1ba0b3b --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs @@ -0,0 +1,118 @@ +using AdaptiveCards; +using AdaptiveCards.Templating; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; +using Microsoft.Agents.Extensions.Teams.Models; +using SearchCommand.Model; +using System.Collections.Specialized; +using System.Web; + +namespace SearchCommand +{ + /// + /// Defines the activity handlers. + /// + public class ActivityHandlers + { + private readonly HttpClient _httpClient; + private readonly string _packageCardFilePath = Path.Combine(".", "Resources", "PackageCard.json"); + + public ActivityHandlers(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient("WebClient"); + } + + /// + /// Handles Message Extension query events. + /// + public QueryHandlerAsync QueryHandler => async (ITurnContext turnContext, ITurnState turnState, Query> query, CancellationToken cancellationToken) => + { + string text = ProtocolJsonSerializer.ToObject(query.Parameters["queryText"]); + int count = query.Count; + + Package[] packages = await SearchPackages(text, count, cancellationToken); + + // Format search results + List attachments = packages.Select(package => new MessagingExtensionAttachment + { + ContentType = HeroCard.ContentType, + Content = new HeroCard + { + Title = package.Id, + Text = package.Description + }, + Preview = new HeroCard + { + Title = package.Id, + Text = package.Description, + Tap = new CardAction + { + Type = "invoke", + Value = package + } + }.ToAttachment() + }).ToList(); + + return new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = attachments + }; + }; + + /// + /// Handles Message Extension selecting item events. + /// + public SelectItemHandlerAsync SelectItemHandler => async (ITurnContext turnContext, ITurnState turnState, object item, CancellationToken cancellationToken) => + { + CardPackage package = ProtocolJsonSerializer.ToObject(item); + string cardTemplate = await File.ReadAllTextAsync(_packageCardFilePath, cancellationToken)!; + string cardContent = new AdaptiveCardTemplate(cardTemplate).Expand(package); + MessagingExtensionAttachment attachment = new() + { + ContentType = AdaptiveCard.ContentType, + Content = ProtocolJsonSerializer.ToJson(cardContent) + }; + + return new MessagingExtensionResult + { + Type = "result", + AttachmentLayout = "list", + Attachments = new List { attachment } + }; + }; + + private async Task SearchPackages(string text, int size, CancellationToken cancellationToken) + { + // Call NuGet Search API + NameValueCollection query = HttpUtility.ParseQueryString(string.Empty); + query["q"] = text; + query["take"] = size.ToString(); + string queryString = query.ToString()!; + string responseContent; + try + { + responseContent = await _httpClient.GetStringAsync($"https://azuresearch-usnc.nuget.org/query?{queryString}", cancellationToken); + } + catch (Exception) + { + throw; + } + + if (!string.IsNullOrWhiteSpace(responseContent)) + { + var responseObj = ProtocolJsonSerializer.ToJsonElements(responseContent); + return ProtocolJsonSerializer.ToObject(responseObj["data"]); + } + else + { + return Array.Empty(); + } + } + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/AspNetExtensions.cs b/src/samples/Application/messageExtensions.a.searchCommand/AspNetExtensions.cs new file mode 100644 index 00000000..bf8ff895 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/AspNetExtensions.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Agents.Authentication; +using Microsoft.IdentityModel.Tokens; +using System.Globalization; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Protocols; +using System.Collections.Concurrent; +using Microsoft.IdentityModel.Validators; +using System.IdentityModel.Tokens.Jwt; + +#nullable disable + +namespace Microsoft.Agents.Samples +{ + public static class AspNetExtensions + { + private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); + + /// + /// Adds token validation typical for ABS/SMBA and Bot-to-bot. + /// default to Azure Public Cloud. + /// + /// + /// + /// Name of the config section to read. + /// Optional logger to use for authentication event logging. + /// + /// Configuration: + /// + /// "TokenValidation": { + /// "Audiences": [ + /// "{required:bot-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// + /// + /// `IsGov` can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. + /// `ValidIssuers` can be omitted, in which case the Public Azure Bot Service issuers are used. + /// `TenantId` can be omitted if the Agent is not being called by another Agent. Otherwise it is used to add other known issuers. Only when `ValidIssuers` is omitted. + /// `AzureBotServiceOpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `OpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `AzureBotServiceTokenHandling` defaults to true and should always be true until Azure Bot Service sends Entra ID token. + /// + public static void AddBotAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string authenticationSection = "TokenValidation", ILogger logger = null) + { + IConfigurationSection tokenValidationSection = configuration.GetSection("TokenValidation"); + + List validTokenIssuers = tokenValidationSection.GetSection("ValidIssuers").Get>(); + + // If ValidIssuers is empty, default for ABS Public Cloud + if (validTokenIssuers == null || validTokenIssuers.Count == 0) + { + validTokenIssuers = + [ + "https://api.botframework.com", + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + ]; + + string tenantId = tokenValidationSection["TenantId"]; + if (!string.IsNullOrEmpty(tenantId)) + { + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId)); + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId)); + } + } + + List audiences = tokenValidationSection.GetSection("Audiences").Get>(); + if (audiences == null || audiences.Count == 0) + { + throw new ArgumentException($"{authenticationSection}:Audiences requires at least one value"); + } + + bool isGov = tokenValidationSection.GetValue("IsGov", false); + var azureBotServiceTokenHandling = tokenValidationSection.GetValue("AzureBotServiceTokenHandling", true); + + // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. + var azureBotServiceOpenIdMetadataUrl = tokenValidationSection["AzureBotServiceOpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(azureBotServiceOpenIdMetadataUrl)) + { + azureBotServiceOpenIdMetadataUrl = isGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; + } + + // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. + var openIdMetadataUrl = tokenValidationSection["OpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(openIdMetadataUrl)) + { + openIdMetadataUrl = isGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; + } + + var openIdRefreshInterval = tokenValidationSection.GetValue("OpenIdMetadataRefresh", BaseConfigurationManager.DefaultAutomaticRefreshInterval); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5), + ValidIssuers = validTokenIssuers, + ValidAudiences = audiences, + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + }; + + // Using Microsoft.IdentityModel.Validators + options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + options.Events = new JwtBearerEvents + { + // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. + OnMessageReceived = async context => + { + var authorizationHeader = context.Request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authorizationHeader)) + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + string[] parts = authorizationHeader?.Split(' '); + if (parts.Length != 2 || parts[0] != "Bearer") + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + JwtSecurityToken token = new(parts[1]); + var issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value; + + if (azureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) + { + // Use the Bot Framework authority for this configuration manager + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(azureBotServiceOpenIdMetadataUrl, key => + { + return new ConfigurationManager(azureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + else + { + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(openIdMetadataUrl, key => + { + return new ConfigurationManager(openIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + + await Task.CompletedTask.ConfigureAwait(false); + }, + + OnTokenValidated = context => + { + logger?.LogDebug("TOKEN Validated"); + return Task.CompletedTask; + }, + OnForbidden = context => + { + logger?.LogWarning(context.Result.ToString()); + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + logger?.LogWarning(context.Exception.ToString()); + return Task.CompletedTask; + } + }; + }); + } + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Config.cs b/src/samples/Application/messageExtensions.a.searchCommand/Config.cs new file mode 100644 index 00000000..9ef63c1b --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Config.cs @@ -0,0 +1,8 @@ +namespace SearchCommand +{ + public class ConfigOptions + { + public string? BOT_ID { get; set; } + public string? BOT_PASSWORD { get; set; } + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs b/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs new file mode 100644 index 00000000..a824f960 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.BotBuilder; + +namespace SearchCommand.Bot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs b/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs new file mode 100644 index 00000000..8bcbd50b --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs @@ -0,0 +1,43 @@ + +namespace SearchCommand.Model +{ + /// + /// The strongly typed NuGet package model for Adaptive Card + /// + public class CardPackage + { + public string? Id { get; set; } + + public string? Version { get; set; } + + public string? Description { get; set; } + + public string? Tags { get; set; } + + public string? Authors { get; set; } + + public string? Owners { get; set; } + + public string? LicenseUrl { get; set; } + + public string? ProjectUrl { get; set; } + + public string? NuGetUrl { get; set; } + + public static CardPackage Create(Package package) + { + return new CardPackage + { + Id = package.Id ?? string.Empty, + Version = package.Version ?? string.Empty, + Description = package.Description ?? string.Empty, + Tags = package.Tags == null ? string.Empty : string.Join(", ", package.Tags), + Authors = package.Authors == null ? string.Empty : string.Join(", ", package.Authors), + Owners = package.Owners == null ? string.Empty : string.Join(", ", package.Owners), + LicenseUrl = package.LicenseUrl ?? string.Empty, + ProjectUrl = package.ProjectUrl ?? string.Empty, + NuGetUrl = $"https://www.nuget.org/packages/{package.Id}" + }; + } + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs b/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs new file mode 100644 index 00000000..d3ac8532 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs @@ -0,0 +1,29 @@ + +namespace SearchCommand.Model +{ + /// + /// The strongly typed NuGet package search result + /// + public class Package + { + public string? Id { get; set; } + + public string? Version { get; set; } + + public string? Description { get; set; } + + public string[]? Tags { get; set; } + + public string[]? Authors { get; set; } + + public string[]? Owners { get; set; } + + public string? IconUrl { get; set; } + + public string? LicenseUrl { get; set; } + + public string? ProjectUrl { get; set; } + + public object[]? PackageTypes { get; set; } + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Program.cs b/src/samples/Application/messageExtensions.a.searchCommand/Program.cs new file mode 100644 index 00000000..1bd9ef7b --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Program.cs @@ -0,0 +1,50 @@ +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Extensions.Teams.App; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using SearchCommand; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +builder.Services.AddSingleton(); + +// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. +builder.AddBot(sp => +{ + TeamsApplicationOptions applicationOptions = new() + { + TurnStateFactory = () => new TurnState(sp.GetService()!) + }; + + TeamsApplication app = new(applicationOptions); + + ActivityHandlers activityHandlers = sp.GetService()!; + + // Listen for search actions + app.MessageExtensions.OnQuery("searchCmd", activityHandlers.QueryHandler); + // Listen for item tap + app.MessageExtensions.OnSelectItem(activityHandlers.SelectItemHandler); + + return app; +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseStaticFiles(); +app.UseRouting(); +app.MapControllers(); + +app.Run(); diff --git a/src/samples/Application/messageExtensions.a.searchCommand/README.md b/src/samples/Application/messageExtensions.a.searchCommand/README.md new file mode 100644 index 00000000..a82734d3 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/README.md @@ -0,0 +1,31 @@ +## Summary + +This sample shows how to incorporate a basic Message Extension app into a Microsoft Teams application using [Bot Framework](https://dev.botframework.com) and the Teams AI SDK. Users can search nuget.org for packages. + +## Set up instructions + +All the samples in the C# .NET SDK can be set up in the same way. You can find the step by step instructions here: + [Setup Instructions](../README.md). + +## Interacting with the Message Extension + +You can interact with this app by selecting its app icon in the chat compose area. This opens a dialog that allows you to search NuGet for a package. Selecting a package will output an Adaptive Card with its description to the chat. + +Here's a sample search result: + +![Sample search](assets/search.png) + +And after selecting it outputs an Adaptive Card. + +![Adaptive Card](assets/card.png) + +## Deploy to Azure + +You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. + +You can find deployment instructions [here](../README.md#deploy-to-azure). + +## Further reading + +- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) +- [How Microsoft Teams bots work](https://learn.microsoft.com/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json b/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json new file mode 100644 index 00000000..a907b097 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "size": "Medium", + "weight": "Bolder", + "text": "${id}" + }, + { + "type": "FactSet", + "facts": [ + { + "title": "Version", + "value": "${version}" + }, + { + "title": "Description", + "value": "${description}" + }, + { + "title": "Tags", + "value": "${tags}" + }, + { + "title": "Authors", + "value": "${authors}" + }, + { + "title": "Owners", + "value": "${owners}" + } + ] + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "NuGet", + "url": "${nugetUrl}" + }, + { + "type": "Action.OpenUrl", + "title": "Project", + "url": "${projectUrl}" + }, + { + "type": "Action.OpenUrl", + "title": "License", + "url": "${licenseUrl}" + } + ] +} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj new file mode 100644 index 00000000..bbbb7085 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj @@ -0,0 +1,40 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + None + + + + + + + + diff --git a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln new file mode 100644 index 00000000..06946a52 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SearchCommand", "SearchCommand.csproj", "{2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Build.0 = Release|Any CPU + {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {436748E9-F64E-4E1D-9BEE-1AE35954FA4F} + EndGlobalSection +EndGlobal diff --git a/src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png b/src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png new file mode 100644 index 0000000000000000000000000000000000000000..f27ccf2036bf2264dc0d11edf2af2bda62e4efdf GIT binary patch literal 1066 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaloCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49xpIT^vIy7~kGC%#(I!aJU%yVD^#PnvNe5 zY1um`Oj+#WC3y9e;Us63+9EE_EXjNxu|}7}jyc6;|Ee3L%wi^d-gxKYxi>fe9)4Wc zxa_YvcME5O3F8DchD$6Cvlu*t88Vp^d>NL|Lr`DL?D4N}c{_jp%(HAg{rPUu*Szg# zj!7mc`&s@7H-CTs|F4$!|Ce$kF#Fm5;7{vuU}%<3{UCovtdW7u?A8PO8JbLtJXu!` z)*E=Uq<`n{|J}Oq&G+9=pRT^{A7^iE9sTdm-|J7arQcTAE#7O4&s)AbmEKG-&pNxS zC@bvpTt>gz>5tZEFHbWKWmwE(Czx~Ggt5o$h06xsU>1W{3Bm_|n8_b_(d@&LeEW~i zg^dTlUYFmmyZpo3^85CcxxEL@T$u4k9$$#sDCao5UUz!>`!fG+?(yZBb?@R92k)%- zop#eIy}>bd?)!h82_c(x{)!wp;MSY4tn@sS#GMSmGuvLoGe{eFu^7LrP;BV6C}r84 z_dQ80!}%J=HG4bxbez$5Ys+@0HQnxRMXMf1ieE3d1B&%|CHbgX^da?eHs6`1kLARhjOac zf3;y1Iq~M{LLS3g_M2bU{+PBvomV=FH7$YTy5I%1<5B$=?>3fqI5P%5iajq7)W9SX p;gpazd1JnvZNlx8HB0WjVJ`J~Q+P@%pA+aZ22WQ%mvv4FO#n^cR9FB2 literal 0 HcmV?d00001 diff --git a/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json b/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json new file mode 100644 index 00000000..e339ac0e --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json @@ -0,0 +1,38 @@ +{ + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Assembly": "Microsoft.Agents.Authentication.Msal", + "Type": "MsalAuth", + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/assets/card.png b/src/samples/Application/messageExtensions.a.searchCommand/assets/card.png new file mode 100644 index 0000000000000000000000000000000000000000..fa101905980845f502d8b29fdf1d15da7ef758be GIT binary patch literal 76643 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

Uvu3KLcb-LHWGsYv-;01@4Nvez=*>?}5OIuH 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 nPH-sK$!s>+l~83p_=W+_J- literal 0 HcmV?d00001 diff --git a/src/samples/Application/messageExtensions.a.searchCommand/assets/search.png b/src/samples/Application/messageExtensions.a.searchCommand/assets/search.png new file mode 100644 index 0000000000000000000000000000000000000000..90438a883199ada4eee6243e973e32bbdfd30aac GIT binary patch literal 61027 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 literal 0 HcmV?d00001 diff --git a/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev b/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev new file mode 100644 index 00000000..7342a87d --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev @@ -0,0 +1,18 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= + +APP_NAME_SUFFIX=dev diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep new file mode 100644 index 00000000..8bf8a23f --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep @@ -0,0 +1,74 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure APP Service from a package file + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json new file mode 100644 index 00000000..1b12ffc8 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "SearchCommandInfra" + } + } +} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md new file mode 100644 index 00000000..e912ea21 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep new file mode 100644 index 00000000..ab67c7a5 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml new file mode 100644 index 00000000..d86cb665 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml @@ -0,0 +1,91 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json +# +# The teamsapp.local.yml composes automation tasks for Teams Toolkit when running locally. +# This file is used when Debugging (F5) from Visual Studio or with the TeamsFx CLI commands. +# i.e. `teamsfx provision --env local` or `teamsfx deploy --env local`. +# +# You can customize this file. +# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +# Visit https://aka.ms/teamsfx-actions for details on actions +version: 1.1.0 + +environmentFolderPath: ./env + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +# Runs during 'Teams Toolkit -> Prepare Teams App Dependencies' or run manually using `teamsfx provision --env local`. +provision: + + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + name: SearchCommand${{APP_NAME_SUFFIX}} + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Automates the creation an Azure AD app registration which is required for a bot. + # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. + - uses: botAadApp/create + with: + name: SearchCommand${{APP_NAME_SUFFIX}} + writeToEnvironmentFile: + botId: BOT_ID + botPassword: SECRET_BOT_PASSWORD + + # Provides the Teams Toolkit .env file values to the apps runtime settings so they can be accessed in source code. + - uses: file/createOrUpdateJsonFile + with: + target: ./appsettings.Development.json + content: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + + # Automates the creation and configuration of a Bot Framework registration which is required for a bot. + # This configures the bot to use the Azure AD app registration created in the previous step. + # Teams Toolkit uses the Visual Studio Dev Tunnel URL and updates BOT_ENDPOINT when debugging (F5). + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: SearchCommand + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. + - uses: teamsApp/validateManifest + with: + manifestPath: ./appPackage/manifest.json + + # Automates the creation of a Teams app package (.zip). + - uses: teamsApp/zipAppPackage + with: + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. + - uses: teamsApp/validateAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. + # This action ensures that any manifest changes are reflected when launching the app again in Teams. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Provides the debug profile to launch the app in Teams when debugging (F5) from Visual Studio. + - uses: file/createOrUpdateJsonFile + with: + target: ./Properties/launchSettings.json + content: + profiles: + Microsoft Teams (browser): + commandName: "Project" + dotnetRunMessages: true + launchBrowser: true + launchUrl: "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + applicationUrl: "http://localhost:5130" + environmentVariables: + ASPNETCORE_ENVIRONMENT: "Development" + hotReloadProfile: "aspnetcore" diff --git a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml new file mode 100644 index 00000000..9b5e1d91 --- /dev/null +++ b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml @@ -0,0 +1,83 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json +# +# The teamsapp.yml composes automation tasks for Teams Toolkit when running other environment configurations. +# This file is used when selecting the Provision, Deploy menu items in the Teams Toolkit menu for Visual Studio +# or with the TeamsFx CLI commands. +# i.e. `teamsfx provision --env {environment name}` or `teamsfx deploy --env {environment name}`. +# +# You can customize this file. +# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +# Visit https://aka.ms/teamsfx-actions for details on actions +version: 1.1.0 + +environmentFolderPath: ./env + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +# Runs with the Provision menu or CLI using `teamsfx provision --env {environment name}`. +provision: + + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + name: SearchCommand${{APP_NAME_SUFFIX}} + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Automates the creation an Azure AD app registration which is required for a bot. + # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. + - uses: botAadApp/create + with: + name: SearchCommand${{APP_NAME_SUFFIX}} + writeToEnvironmentFile: + botId: BOT_ID + botPassword: SECRET_BOT_PASSWORD + + # Automates the creation of infrastructure defined in ARM templates to host the bot. + # The created resource IDs are saved to an environment file. + - uses: arm/deploy + with: + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep + parameters: ./infra/azure.parameters.json + deploymentName: Create-resources-SearchCommand-${{TEAMSFX_ENV}} + bicepCliVersion: v0.9.1 + + # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. + - uses: teamsApp/validateManifest + with: + manifestPath: ./appPackage/manifest.json + + # Automates creating a final app package (.zip) by replacing any variables in the manifest.json file for the current environment. + - uses: teamsApp/zipAppPackage + with: + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. + - uses: teamsApp/validateAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. + # This action ensures that any manifest changes are reflected when launching the app again in Teams. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Defines what the `deploy` lifecycle step does with Teams Toolkit. +# Runs with the Deploy menu item or CLI using `teamsfx deploy --env {environment name}`. +deploy: + + # Install any dependencies and build the web app using .NET CLI. + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + + # Deploy to an Azure App Service using the artifact created in the above step. + - uses: azureAppService/zipDeploy + with: + artifactFolder: bin/Release/net6.0/win-x86/publish + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} From 85f4f2c409be69769dba8ab1f522af72c2a681df Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 24 Feb 2025 12:04:09 -0600 Subject: [PATCH 46/60] Initial User Authentication with Token Service flows (WIP) --- src/Microsoft.Agents.SDK.sln | 7 + .../Prompts/OAuthPrompt.cs | 16 +- .../Prompts/OAuthPromptSettings.cs | 47 +-- .../App/AdaptiveCards/AdaptiveCardsFeature.cs | 1 + .../App/Application.cs | 345 ++++++++++-------- .../App/ApplicationOptions.cs | 11 +- .../App/Authentication/AuthException.cs | 7 + .../App/Authentication/AuthUtilities.cs | 101 +++++ .../Authentication/AuthenticationManager.cs | 143 ++++++++ .../Authentication/AuthenticationOptions.cs | 77 ++++ .../App/Authentication/IAuthentication.cs | 81 +--- .../App/Authentication/SignInResponse.cs | 38 ++ .../App/Authentication/SignInStatus.cs | 26 ++ .../TokenService}/ActivityUtilities.cs | 2 +- .../TokenService/DedupeTokenExchange.cs | 204 +++++++++++ .../TokenService/OAuthAuthentication.cs | 108 ++++++ .../TokenService/OAuthBotAuthentication.cs | 188 ++++++++++ .../Authentication/TokenService}/OAuthFlow.cs | 149 ++++---- .../TokenService/OAuthSettings.cs | 70 ++++ .../TokenService/UserTokenClientWrapper.cs | 48 +++ .../App/StreamingResponse.cs | 3 +- .../App/TypingTimer.cs | 16 +- .../ChannelAdapter.cs | 10 + .../ChannelServiceAdapterBase.cs | 10 +- .../Compat/AutoSaveStateMiddleware.cs | 4 +- .../IChannelAdapter.cs | 13 + .../OAuthTurnStateConstants.cs | 21 -- .../State/BotState.cs | 18 + .../State/TempState.cs | 13 + .../RestClients/UserTokenRestClient.cs | 1 + .../Serialization/ProtocolJsonSerializer.cs | 8 +- .../MessageExtensionsFeature.cs | 1 + .../App/TaskModules/TaskModulesFeature.cs | 1 + .../App/TeamsApplication.cs | 1 + .../ActivityTaskQueue.cs | 27 +- .../ActivityWithClaims.cs | 12 +- .../HostedActivityService.cs | 37 +- .../IActivityTaskQueue.cs | 6 +- .../HostedTaskService.cs | 2 +- .../Hosting/AspNetCore/CloudAdapter.cs | 29 +- .../BlobsStorage.cs | 2 +- .../Microsoft.Agents.Storage/EtagException.cs | 22 ++ .../Microsoft.Agents.Storage/MemoryStorage.cs | 2 +- .../AuthenticationBot.csproj | 18 + .../AuthenticationBot/BotController.cs | 25 ++ .../Application/AuthenticationBot/Program.cs | 111 ++++++ .../Application/AuthenticationBot/README.md | 97 +++++ .../AuthenticationBot/StateExtensions.cs | 27 ++ .../AuthenticationBot/appManifest/color.png | Bin 0 -> 3415 bytes .../appManifest/manifest.json | 48 +++ .../AuthenticationBot/appManifest/outline.png | Bin 0 -> 407 bytes .../AuthenticationBot/appsettings.json | 37 ++ .../messaging.echoBot/EchoBotApplication.cs | 60 --- .../Application/messaging.echoBot/Program.cs | 43 ++- src/samples/AuthenticationBot/AuthBot.cs | 6 +- .../Bot1/BotHostAdapterWithErrorHandler.cs | 5 +- .../Bot2/BotAdapterWithErrorHandler.cs | 7 +- .../BotAdapterWithErrorHandler.cs | 7 +- .../Bots/TeamsConversationBot.cs | 2 +- .../Bots/InMeetingNotifications.cs | 2 +- .../Bots/ActivityBot.cs | 8 +- .../bot-request-approval/Bots/ActivityBot.cs | 8 +- .../BotBuilder.Testing/TestAdapterTests.cs | 12 +- .../ReplaceDialogTest.cs | 2 +- .../App/AdaptiveCardsTests.cs | 21 +- .../App/ApplicationRouteTests.cs | 89 ++--- .../App/TestUtils/TurnStateConfig.cs | 3 +- .../ChannelAdapterBracketingTest.cs | 12 +- .../Handler/ActivityHandlerTests.cs | 10 +- .../OAuthFlowTests.cs | 24 +- .../OnTurnErrorTests.cs | 3 +- .../App/ApplicationRouteTests.cs | 95 ++--- .../App/MeetingsTests.cs | 9 +- .../App/MessageExtensionsTests.cs | 55 +-- .../App/TaskModulesTests.cs | 13 +- .../HostedActivityServiceTests.cs | 19 +- .../CloudAdapterTests.cs | 9 +- .../AutoSaveStateMiddlewareTests.cs | 8 +- .../TurnStateTests.cs | 13 +- .../BlobsStorageTests.cs | 2 +- 80 files changed, 2124 insertions(+), 714 deletions(-) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{ => Authentication/TokenService}/ActivityUtilities.cs (88%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{ => App/Authentication/TokenService}/OAuthFlow.cs (76%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs create mode 100644 src/libraries/Storage/Microsoft.Agents.Storage/EtagException.cs create mode 100644 src/samples/Application/AuthenticationBot/AuthenticationBot.csproj create mode 100644 src/samples/Application/AuthenticationBot/BotController.cs create mode 100644 src/samples/Application/AuthenticationBot/Program.cs create mode 100644 src/samples/Application/AuthenticationBot/README.md create mode 100644 src/samples/Application/AuthenticationBot/StateExtensions.cs create mode 100644 src/samples/Application/AuthenticationBot/appManifest/color.png create mode 100644 src/samples/Application/AuthenticationBot/appManifest/manifest.json create mode 100644 src/samples/Application/AuthenticationBot/appManifest/outline.png create mode 100644 src/samples/Application/AuthenticationBot/appsettings.json delete mode 100644 src/samples/Application/messaging.echoBot/EchoBotApplication.cs diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index c56780d1..be305884 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -153,6 +153,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{E08758 samples\Shared\AspNetExtensions.cs = samples\Shared\AspNetExtensions.cs EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBot", "samples\Application\AuthenticationBot\AuthenticationBot.csproj", "{0ACB6011-D08C-43D5-B4BA-13E270DF24AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -389,6 +391,10 @@ Global {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Build.0 = Release|Any CPU {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Deploy.0 = Release|Any CPU + {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -463,6 +469,7 @@ Global {348A61E5-E16A-47ED-B621-F2C61E1E316D} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} {AC149C90-3191-4995-B6F5-7EA35F311EAB} = {851DB8B0-CD62-414F-B370-EC3680563B6E} {E08758F4-9774-472A-B0E0-CA057F6296AA} = {674A812C-7287-4883-97F9-697D83750648} + {0ACB6011-D08C-43D5-B4BA-13E270DF24AE} = {851DB8B0-CD62-414F-B370-EC3680563B6E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 1d8faa7f..4e256808 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Authentication; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; @@ -81,12 +82,7 @@ public OAuthPrompt(string dialogId, OAuthPromptSettings settings, PromptValidato _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _validator = validator; - _dialogOAuthFlow = new OAuthFlow( - _settings.Title, - _settings.Text, - _settings.ConnectionName, - _settings.Timeout, - _settings.ShowSignInLink); + _dialogOAuthFlow = new OAuthFlow(_settings); } ///

@@ -141,7 +137,7 @@ public override async Task BeginDialogAsync(DialogContext dc, } // Initialize state - var timeout = _settings.Timeout ?? (int)OAuthTurnStateConstants.OAuthLoginTimeoutValue.TotalMilliseconds; + var timeout = _settings.Timeout ?? (int)OAuthPromptSettings.DefaultTimeoutValue.TotalMilliseconds; var state = dc.ActiveDialog.State; state[PersistedOptions] = opt; state[PersistedState] = new Dictionary @@ -152,7 +148,7 @@ public override async Task BeginDialogAsync(DialogContext dc, state[PersistedExpires] = DateTime.UtcNow.AddMilliseconds(timeout); SetCallerInfoInDialogState(state, dc.Context); - var token = await _dialogOAuthFlow.BeginFlowAsync(dc.Context, opt?.Prompt, cancellationToken).ConfigureAwait(false); + var token = await _dialogOAuthFlow.BeginFlowAsync(dc.Context, () => Task.FromResult(opt?.Prompt), cancellationToken).ConfigureAwait(false); if (token != null) { // Return token @@ -260,7 +256,7 @@ public override async Task ContinueDialogAsync(DialogContext d /// the result contains the user's token. public async Task GetUserTokenAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - return await OAuthFlow.GetTokenClient(turnContext).GetUserTokenAsync(turnContext.Activity.From.Id, _settings.ConnectionName, turnContext.Activity.ChannelId, magicCode: null, cancellationToken).ConfigureAwait(false); + return await UserTokenClientWrapper.GetUserTokenAsync(turnContext, _settings.ConnectionName, magicCode: null, cancellationToken).ConfigureAwait(false); } /// @@ -272,7 +268,7 @@ public async Task GetUserTokenAsync(ITurnContext turnContext, Can /// A task that represents the work queued to execute. public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { - await OAuthFlow.GetTokenClient(turnContext).SignOutUserAsync(turnContext.Activity.From.Id, _settings.ConnectionName, turnContext.Activity.ChannelId, cancellationToken).ConfigureAwait(false); + await UserTokenClientWrapper.SignOutUserAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false); } public static async Task CreateConnectorClientAsync(ITurnContext turnContext, string serviceUrl, ClaimsIdentity claimsIdentity, string audience, CancellationToken cancellationToken) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs index 8c3f0e1b..3ae3a5e0 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs @@ -1,55 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; + namespace Microsoft.Agents.BotBuilder.Dialogs { /// /// Contains settings for an . /// - public class OAuthPromptSettings + public class OAuthPromptSettings : OAuthSettings { - /// - /// Gets or sets the name of the OAuth connection. - /// - /// The name of the OAuth connection. - public string ConnectionName { get; set; } - - /// - /// Gets or sets the title of the sign-in card. - /// - /// The title of the sign-in card. - public string Title { get; set; } - - /// - /// Gets or sets any additional text to include in the sign-in card. - /// - /// Any additional text to include in the sign-in card. - public string Text { get; set; } - - /// - /// Gets or sets the number of milliseconds the prompt waits for the user to authenticate. - /// Default is 900,000 (15 minutes). - /// - /// The number of milliseconds the prompt waits for the user to authenticate. - public int? Timeout { get; set; } - - /// - /// Gets or sets a value indicating whether the should end upon - /// receiving an invalid message. Generally the will ignore - /// incoming messages from the user during the auth flow, if they are not related to the - /// auth flow. This flag enables ending the rather than - /// ignoring the user's message. Typically, this flag will be set to 'true', but is 'false' - /// by default for backwards compatibility. - /// - /// True if the should automatically end upon receiving - /// an invalid message. - public bool EndOnInvalidMessage { get; set; } - - /// - /// Gets or sets an optional boolean value to force the display of a Sign In link overriding - /// the default behavior. - /// - /// True to display the SignInLink. - public bool? ShowSignInLink { get; set; } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index 24969d4e..d147429c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 4684ba01..71e65d9f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -2,14 +2,18 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.App.Authentication; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using static Microsoft.Agents.BotBuilder.App.Authentication.AuthenticationManager; namespace Microsoft.Agents.BotBuilder.App { @@ -18,8 +22,7 @@ namespace Microsoft.Agents.BotBuilder.App /// public class Application : IBot { - //TODO - //private readonly AuthenticationManager? _authentication; + private readonly AuthenticationManager _authentication; private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; @@ -30,8 +33,7 @@ public class Application : IBot private readonly ConcurrentQueue _beforeTurn; private readonly ConcurrentQueue _afterTurn; - // TODO - //private readonly SelectorAsync? _startSignIn; + private readonly SelectorAsync? _startSignIn; /// /// Creates a new Application instance. @@ -57,11 +59,9 @@ public Application(ApplicationOptions options) _beforeTurn = new ConcurrentQueue(); _afterTurn = new ConcurrentQueue(); - //TODO - /* if (options.Authentication != null) { - _authentication = new AuthenticationManager(this, options.Authentication, options.Storage); + _authentication = new AuthenticationManager(this, options.Authentication); if (options.Authentication.AutoSignIn != null) { @@ -69,10 +69,10 @@ public Application(ApplicationOptions options) } else { + // If AutoSignIn wasn't specified, default to true. _startSignIn = (context, cancellationToken) => Task.FromResult(true); } } - */ } /// @@ -80,14 +80,11 @@ public Application(ApplicationOptions options) /// public AdaptiveCardsFeature AdaptiveCards { get; } - //TODO - /* /// /// Accessing authentication specific features. /// - public AuthenticationManager Authentication + public AuthenticationManager Authentication { - get { if (_authentication == null) @@ -98,13 +95,13 @@ public AuthenticationManager Authentication return _authentication; } } - */ /// /// The application's configured options. /// public ApplicationOptions Options { get; } + #region Route Handling /// /// Adds a new route to the application. /// @@ -230,33 +227,33 @@ public virtual Application OnConversationUpdate(string conversationUpdateEvent, switch (conversationUpdateEvent) { case ConversationUpdateEvents.MembersAdded: - { - routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - && context.Activity?.MembersAdded != null - && context.Activity.MembersAdded.Count > 0 - ); - break; - } + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersAdded != null + && context.Activity.MembersAdded.Count > 0 + ); + break; + } case ConversationUpdateEvents.MembersRemoved: - { - routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - && context.Activity?.MembersRemoved != null - && context.Activity.MembersRemoved.Count > 0 - ); - break; - } + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && context.Activity?.MembersRemoved != null + && context.Activity.MembersRemoved.Count > 0 + ); + break; + } default: - { - routeSelector = (context, _) => Task.FromResult - ( - string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) - ); - break; - } + { + routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + ); + break; + } } AddRoute(routeSelector, handler, isInvokeRoute: false); return this; @@ -483,22 +480,9 @@ public Application OnAfterTurn(TurnEventHandlerAsync handler) return this; } - /// - /// Called by the adapter (for example, a ) - /// at runtime in order to process an inbound . - /// - /// The context object for this turn. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnContext.Activity); - - await _OnTurnAsync(turnContext, cancellationToken); - } + #endregion + #region ShowTyping /// /// Manually start a timer to periodically send "typing" activities. /// @@ -539,10 +523,93 @@ public void StopTypingTimer() _typingTimer = null; } + #endregion + + #region User Authentication + + /// + /// If the user is signed in, get the access token. If not, triggers the sign in flow for the provided authentication setting name + /// and returns.In this case, the bot should end the turn until the sign in flow is completed. + /// + /// + /// Use this method to get the access token for a user that is signed in to the bot. + /// If the user isn't signed in, this method starts the sign-in flow. + /// The bot should end the turn in this case until the sign-in flow completes and the user is signed in. + /// + /// The turn context. + /// + /// The name of the authentication setting. + /// + /// The cancellation token. + /// + public Task GetTokenOrStartSignInAsync(ITurnContext turnContext, ITurnState turnState, string settingName, SignInCompletionHandlerAsync completionHandler, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + + /* + // TODO: AddRoute + + // User is currently not in sign in flow + if (AuthUtilities.UserInSignInFlow(turnState) == null) + { + AuthUtilities.SetUserInSignInFlow(turnState, settingName); + } + else + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + throw new InvalidOperationException("Invalid sign in flow state. Cannot start sign in when already started"); + } + + SignInResponse response = await Authentication.SignUserInAsync(turnContext, settingName, cancellationToken).ConfigureAwait(false); + + if (response.Status == SignInStatus.Error) + { + string message = response.Error!.ToString(); + + // TODO: the current activity shouldn't trigger an error in the case of manual signin. Problem is, the flow impls + // currently ignore all but specific activities. + if (response.Cause == AuthExceptionReason.InvalidActivity) + { + message = $"User is not signed in and cannot start sign in flow for this activity: {response.Error}"; + } + + throw new InvalidOperationException($"Error occurred while trying to authenticate user: {message}, flow: {settingName}"); + } + + // Call the handler immediately if the user was already signed in. + if (response.Status == SignInStatus.Complete) + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + AuthUtilities.SetTokenInState(turnState, settingName, response.TokenResponse.Token); + await completionHandler(turnContext, turnState, settingName, response, cancellationToken).ConfigureAwait(false); + } + + */ + } + + #endregion + + #region Turn Handling + /// + /// Called by the adapter (for example, a ) + /// at runtime in order to process an inbound . + /// + /// The context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(turnContext); + ArgumentNullException.ThrowIfNull(turnContext.Activity); + + await _OnTurnAsync(turnContext, cancellationToken); + } + /// /// Internal method to wrap the logic of handling a bot turn. /// - private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) { try { @@ -562,47 +629,83 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // Load turn state ITurnState turnState = Options.TurnStateFactory!(); - await turnState!.LoadStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); - //TODO - /* - // If user is in sign in flow, return the authentication setting name - string? settingName = AuthUtilities.UserInSignInFlow(turnState); - bool shouldStartSignIn = _startSignIn != null && await _startSignIn(turnContext, cancellationToken); - - // Sign the user in - if (this._authentication != null && (shouldStartSignIn || settingName != null)) + // Handle user auth + if (_authentication != null) { - if (settingName == null) + // If a flow is active, continue that. + string? flowName = AuthUtilities.UserInSignInFlow(turnState); + bool shouldStartSignIn = _startSignIn != null && await _startSignIn(turnContext, cancellationToken); + + if (shouldStartSignIn || flowName != null) { - settingName = this._authentication.Default; - } + if (flowName == null) + { + // Auth flow hasn't start yet. + flowName = _authentication.Default; - // Sets the setting name in the context object. It is used in `signIn/verifyState` & `signIn/tokenExchange` route selectors. - BotAuthenticationBase.SetSettingNameInContextActivityValue(turnContext, settingName); + // Bank the Activity so it can be executed after sign in is complete. + AuthUtilities.SetUserSigninActivity(turnContext, turnState); + } - SignInResponse response = await this._authentication.SignUserInAsync(turnContext, turnState, settingName); + SignInResponse response = await _authentication.SignUserInAsync(turnContext, flowName, cancellationToken: cancellationToken).ConfigureAwait(false); - if (response.Status == SignInStatus.Complete) - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - } + if (response.Status == SignInStatus.Pending) + { + // Requires user action, save state and stop processing current activity. Done with this turn. + AuthUtilities.SetUserInSignInFlow(turnState, flowName); + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + return; + } - if (response.Status == SignInStatus.Pending) - { - // Requires user action, save state and stop processing current activity - await turnState.SaveStateAsync(turnContext, storage); - return; - } + if (response.Status == SignInStatus.Error && response.Cause != AuthExceptionReason.InvalidActivity) + { + await _authentication.Get(flowName).ResetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); + AuthUtilities.DeleteUserInSignInFlow(turnState); + AuthUtilities.DeleteUserSigninActivity(turnState); + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + + if (Options.Authentication.SignInFailedMessage == null) + { + throw response.Error; + } + + await turnContext.SendActivitiesAsync(Options.Authentication.SignInFailedMessage(flowName, response), cancellationToken).ConfigureAwait(false); + return; + } - if (response.Status == SignInStatus.Error && response.Cause != AuthExceptionReason.InvalidActivity) - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - throw new TeamsAIException("An error occurred when trying to sign in.", response.Error!); + if (response.Status == SignInStatus.Complete) + { + AuthUtilities.DeleteUserInSignInFlow(turnState); + AuthUtilities.SetTokenInState(turnState, flowName, response.TokenResponse.Token); + // TODO: should probably call "_authentication.Get(flowName).ResetState?", but is this safe? + + var signInActivity = AuthUtilities.DeleteUserSigninActivity(turnState); + if (signInActivity != null) + { + // If the current activity matches the one used to trigger sign in, then + // this is because the user received a token that didn't involve a multi-turn + // flow. No further action needed. + if (!ProtocolJsonSerializer.Equals(signInActivity, turnContext.Activity)) + { + // Since we could be handling an Invoke in this turn, and ITurnContext.Activity is readonly, + // we need to continue the conversation in a different turn with the original Activity that triggered sign in. + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + await Options.Adapter.ProcessProactiveAsync(turnContext.Identity, signInActivity, this, cancellationToken).ConfigureAwait(false); + return; + } + } + + if (Options.Authentication.CompletedMessage != null) + { + await turnContext.SendActivitiesAsync(Options.Authentication.CompletedMessage(flowName, response), cancellationToken).ConfigureAwait(false); + } + } + + // If we got this far, fall through to normal Activity route handling. } } - */ // Call before turn handler foreach (TurnEventHandlerAsync beforeTurnHandler in _beforeTurn) @@ -618,15 +721,6 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } } - /* - // Populate {{$temp.input}} - if ((turnState.Temp.Input == null || turnState.Temp.Input.Length == 0) && turnContext.Activity.Text != null) - { - // Use the received activity text - turnState.Temp.Input = turnContext.Activity.Text; - } - */ - // Download any input files IList? fileDownloaders = this.Options.FileDownloaders; if (fileDownloaders != null && fileDownloaders.Count > 0) @@ -688,67 +782,6 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc } } - //TODO - /* - /// - /// If the user is signed in, get the access token. If not, triggers the sign in flow for the provided authentication setting name - /// and returns.In this case, the bot should end the turn until the sign in flow is completed. - /// - /// - /// Use this method to get the access token for a user that is signed in to the bot. - /// If the user isn't signed in, this method starts the sign-in flow. - /// The bot should end the turn in this case until the sign-in flow completes and the user is signed in. - /// - /// The turn context. - /// The turn state. - /// The name of the authentication setting. - /// The cancellation token. - /// The access token for the user if they are signed, otherwise null. - /// - public async Task GetTokenOrStartSignInAsync(ITurnContext turnContext, TState turnState, string settingName, CancellationToken cancellationToken = default) - { - string? token = await Authentication.Get(settingName).IsUserSignedInAsync(turnContext, cancellationToken); - - if (token != null) - { - AuthUtilities.SetTokenInState(turnState, settingName, token); - AuthUtilities.DeleteUserInSignInFlow(turnState); - return token; - } - - // User is currently not in sign in flow - if (AuthUtilities.UserInSignInFlow(turnState) == null) - { - AuthUtilities.SetUserInSignInFlow(turnState, settingName); - } - else - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - throw new TeamsAIException("Invalid sign in flow state. Cannot start sign in when already started"); - } - - SignInResponse response = await Authentication.SignUserInAsync(turnContext, turnState, settingName); - - if (response.Status == SignInStatus.Error) - { - string message = response.Error!.ToString(); - if (response.Cause == AuthExceptionReason.InvalidActivity) - { - message = $"User is not signed in and cannot start sign in flow for this activity: {response.Error}"; - } - - throw new TeamsAIException($"Error occured while trying to authenticate user: {message}"); - } - - if (response.Status == SignInStatus.Complete) - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - return turnState.Temp.AuthTokens[settingName]; - } - - // response.Status == SignInStatus.Pending - return null; - } - */ - } + #endregion + } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index e083d5d0..46696930 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -3,9 +3,11 @@ using Microsoft.Extensions.Logging; using System; -using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using System.Collections.Generic; +using Microsoft.Agents.BotBuilder.App.Authentication; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Storage; namespace Microsoft.Agents.BotBuilder.App { @@ -14,6 +16,8 @@ namespace Microsoft.Agents.BotBuilder.App /// public class ApplicationOptions { + public IChannelAdapter? Adapter { get; set; } + /// /// Optional. Options used to customize the processing of Adaptive Card requests. /// @@ -49,12 +53,9 @@ public class ApplicationOptions /// public bool StartTypingTimer { get; set; } = true; - //TODO - /* /// /// Optional. Options used to enable authentication for the application. /// - public AuthenticationOptions? Authentication { get; set; } - */ + public AuthenticationOptions Authentication { get; set; } } } \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs index dad6839a..d3418929 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs @@ -20,6 +20,13 @@ public enum AuthExceptionReason /// InvalidActivity, + /// + /// The the flow timed out (C2 didn't respond in time). + /// + Timeout, + + InvalidSignIn, + /// /// Other error. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs new file mode 100644 index 00000000..ac08b1ef --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; + +namespace Microsoft.Agents.BotBuilder.App.Authentication +{ + /// + /// Authentication utilities + /// + internal class AuthUtilities + { + private const string IS_SIGNED_IN_KEY = "__InSignInFlow__"; + private const string SIGNIN_ACTIVITY_KEY = "__SignInFlowActivity__"; + + public static string GetTokenInState(ITurnState turnState, string name) + { + if (turnState.Temp.AuthTokens.TryGetValue(name, out var token)) + { + return token; + } + return null; + } + + /// + /// Set token in state + /// + /// The turn state + /// The name of token + /// The value of token + public static void SetTokenInState(ITurnState state, string name, string token) + { + state.Temp.AuthTokens[name] = token; + } + + /// + /// Delete token from turn state + /// + /// The turn state + /// The name of token + public static void DeleteTokenFromState(ITurnState turnState, string name) + { + if (turnState.Temp.AuthTokens != null && turnState.Temp.AuthTokens.ContainsKey(name)) + { + turnState.Temp.AuthTokens.Remove(name); + } + } + + /// + /// Determines if the user is in the sign in flow. + /// + /// The turn state. + /// The connection setting name if the user is in sign in flow. Otherwise null. + public static string? UserInSignInFlow(ITurnState turnState) + { + string? value = turnState.User.GetValue(IS_SIGNED_IN_KEY); + + if (value == string.Empty || value == null) + { + return null; + } + + return value; + } + + /// + /// Update the turn state to indicate the user is in the sign in flow by providing the authentication setting name used. + /// + /// The turn state. + /// The connection setting name defined when configuring the authentication options within the application class. + public static void SetUserInSignInFlow(ITurnState turnState, string settingName) + { + turnState.User.SetValue(IS_SIGNED_IN_KEY, settingName); + } + + /// + /// Delete the user in sign in flow state from the turn state. + /// + /// The turn state. + public static void DeleteUserInSignInFlow(ITurnState turnState) + { + turnState.User.DeleteValue(IS_SIGNED_IN_KEY); + } + + public static void SetUserSigninActivity(ITurnContext turnContext, ITurnState turnState) + { + turnState.User.SetValue(SIGNIN_ACTIVITY_KEY, turnContext.Activity); + } + + public static IActivity DeleteUserSigninActivity(ITurnState turnState) + { + var activity = turnState.User.GetValue(SIGNIN_ACTIVITY_KEY); + if (activity != null) + { + turnState.User.DeleteValue(SIGNIN_ACTIVITY_KEY); + } + return activity; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs new file mode 100644 index 00000000..84c49c80 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App.Authentication +{ + /// + /// The user authentication manager + /// + public class AuthenticationManager + { + public delegate Task SignInCompletionHandlerAsync(ITurnContext turnContext, ITurnState turnState, string settingName, SignInResponse completionResponse, CancellationToken cancellationToken); + + private Dictionary _authentications; + + /// + /// The default authentication setting name. + /// + public string Default { get; } + + /// + /// Creates a new instance of the class + /// + /// The application. + /// The authentication options + /// The storage to use. + /// Throws when the options does not contain authentication handlers + public AuthenticationManager(Application app, AuthenticationOptions options) + { + if (options._authenticationSettings.Count == 0) + { + throw new ArgumentException("Authentications setting is empty"); + } + + _authentications = []; + + // If developer does not specify default authentication, set default to the first one in the options + Default = options.Default ?? options._authenticationSettings.First().Key; + + foreach (string key in options._authenticationSettings.Keys) + { + object setting = options._authenticationSettings[key]; + if (setting is OAuthSettings oauthSetting) + { + _authentications.Add(key, new OAuthAuthentication(key, oauthSetting, options.Storage)); + } + //else if (setting is TeamsSsoSettings teamsSsoSettings) + //{ + // _authentications.Add(key, new TeamsSsoAuthentication(app, key, teamsSsoSettings, storage)); + //} + } + } + + /// + /// Sign in a user. + /// + /// + /// On success, this will put the token in ITurnState.Temp.AuthTokens[settingName]. + /// + /// The turn context + /// The name of the authentication handler to use. If null, the default handler name is used. + /// The cancellation token + /// The sign in status + public async Task SignUserInAsync(ITurnContext context, string settingName, CancellationToken cancellationToken = default) + { + settingName = settingName ?? Default; + + IAuthentication auth = Get(settingName); + TokenResponse token; + try + { + token = await auth.SignInUserAsync(context, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + SignInResponse newResponse = new(SignInStatus.Error) + { + Error = ex, + Cause = AuthExceptionReason.Other + }; + if (ex is AuthException authEx) + { + newResponse.Cause = authEx.Cause; + } + + return newResponse; + } + + if (token != null) + { + return new SignInResponse(SignInStatus.Complete) + { + TokenResponse = token + }; + } + + return new SignInResponse(SignInStatus.Pending); + } + + /// + /// Signs out a user. + /// + /// The turn context + /// The turn state + /// Optional. The name of the authentication handler to use. If not specified, the default handler name is used. + /// The cancellation token + public async Task SignOutUserAsync(ITurnContext context, ITurnState state, string? settingName = null, CancellationToken cancellationToken = default) + { + if (settingName == null) + { + settingName = Default; + } + + IAuthentication auth = Get(settingName); + await auth.SignOutUserAsync(context, cancellationToken).ConfigureAwait(false); + AuthUtilities.DeleteTokenFromState(state, settingName); + } + + /// + /// Get an authentication class via name + /// + /// The name of authentication class + /// The authentication class + /// When cannot find the class with given name + public IAuthentication Get(string name) + { + if (_authentications.ContainsKey(name)) + { + return _authentications[name]; + } + + throw new InvalidOperationException($"Could not find authentication handler with name '{name}'."); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs new file mode 100644 index 00000000..b22c4860 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Storage; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App.Authentication +{ + /// + /// Function for determining whether authentication should be enabled for an activity. + /// + /// Context for the current turn of conversation with the user. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// True if authentication should be enabled. Otherwise, False. + public delegate Task SelectorAsync(ITurnContext turnContext, CancellationToken cancellationToken); + + /// + /// Options for authentication. + /// + public class AuthenticationOptions + { + /// + /// The authentication settings to sign-in and sign-out users. + /// Key uniquely identifies each authentication. + /// + internal Dictionary _authenticationSettings { get; set; } + + /// + /// Describes the authentication class the bot should use if the user does not specify a authentication class name. + /// If the value is not provided, the first one in `Authentications` setting will be used as the default one. + /// + public string? Default { get; set; } + + /// + /// The IStorage used by all IAuthentication instances. + /// + public IStorage Storage { get; set; } + + /// + /// Indicates whether the bot should start the sign in flow when the user sends a message to the bot or triggers a message extension. + /// If the selector returns false, the bot will not start the sign in flow before routing the activity to the bot logic. + /// If the selector is not provided, the sign in will always happen for valid activities. + /// + public SelectorAsync? AutoSignIn { get; set; } + + public Func CompletedMessage { get; set; } + + public Func SignInFailedMessage { get; set; } = (string flowName, SignInResponse response) => + [MessageFactory.Text(string.Format("Sign in for '{0}' completed without a token. Status={1}", flowName, response.Cause))]; + + /// + /// Configures the options to add an OAuth authentication setting. + /// + /// The authentication name. + /// The OAuth settings + /// The object for chaining purposes. + public AuthenticationOptions AddAuthentication(string name, OAuthSettings oauthSettings) + { + _authenticationSettings.Add(name, oauthSettings); + return this; + } + + /// + /// The authentication options constructor. + /// + public AuthenticationOptions() + { + _authenticationSettings = []; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs index 37d310d1..70287648 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs @@ -1,64 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.State; -using System; +using Microsoft.Agents.Core.Models; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.BotBuilder.App.Authentication { - /// - /// The sign-in status - /// - public enum SignInStatus - { - /// - /// Sign-in not complete and requires user interaction - /// - Pending, - - /// - /// Sign-in complete - /// - Complete, - - /// - /// Error occurred during sign-in - /// - Error - } - - /// - /// The sign-in response - /// - public class SignInResponse - { - /// - /// The sign-in status - /// - public SignInStatus Status { get; set; } - - /// - /// The exception object. Only available when sign-in status is Error. - /// - public Exception? Error { get; set; } - - /// - /// The cause of error. Only available when sign-in status is Error. - /// - public AuthExceptionReason? Cause { get; set; } - - /// - /// Initialize an instance of current class - /// - /// The sign in status - public SignInResponse(SignInStatus status) - { - this.Status = status; - } - } - /// /// Handles user sign-in and sign-out. /// @@ -72,7 +20,7 @@ public interface IAuthentication /// Application state. /// The cancellation token /// The authentication token if user is signed in. Otherwise returns null. In that case the bot will attempt to sign the user in. - Task SignInUserAsync(ITurnContext context, ITurnState state, CancellationToken cancellationToken = default); + Task SignInUserAsync(ITurnContext context, CancellationToken cancellationToken = default); /// /// Signs out a user. @@ -80,21 +28,7 @@ public interface IAuthentication /// Current turn context. /// Application state. /// The cancellation token - Task SignOutUserAsync(ITurnContext context, ITurnState state, CancellationToken cancellationToken = default); - - /// - /// The handler function is called when the user has successfully signed in - /// - /// The handler function to call when the user has successfully signed in - /// The class itself for chaining purpose - IAuthentication OnUserSignInSuccess(Func handler); - - /// - /// The handler function is called when the user sign in flow fails - /// - /// The handler function to call when the user failed to signed in - /// The class itself for chaining purpose - IAuthentication OnUserSignInFailure(Func handler); + Task SignOutUserAsync(ITurnContext context, CancellationToken cancellationToken = default); /// /// Check if the user is signed, if they are then return the token. @@ -103,5 +37,14 @@ public interface IAuthentication /// The cancellation token /// The token if the user is signed. Otherwise null. Task IsUserSignedInAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); + + /// + /// Resets the sign in flow state. + /// + /// + /// + /// + /// + Task ResetStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default); } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs new file mode 100644 index 00000000..4e5e336d --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Models; +using System; + +namespace Microsoft.Agents.BotBuilder.App.Authentication +{ + /// + /// The sign-in response + /// + /// + /// Initialize an instance of current class + /// + /// The sign in status + public class SignInResponse(SignInStatus status) + { + /// + /// The sign-in status + /// + public SignInStatus Status { get; set; } = status; + + /// + /// The exception object. Only available when sign-in status is Error. + /// + public Exception? Error { get; set; } + + /// + /// The cause of error. Only available when sign-in status is Error. + /// + public AuthExceptionReason? Cause { get; set; } + + /// + /// The token resonse. Only available when sign-in status is Complete. + /// + public TokenResponse TokenResponse { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs new file mode 100644 index 00000000..8106f844 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.App.Authentication +{ + /// + /// The sign-in status + /// + public enum SignInStatus + { + /// + /// Sign-in not complete and requires user interaction + /// + Pending, + + /// + /// Sign-in complete + /// + Complete, + + /// + /// Error occurred during sign-in + /// + Error + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs similarity index 88% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs index 01a8c406..cdeee211 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Net; -namespace Microsoft.Agents.BotBuilder.App +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService { public static class ActivityUtilities { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs new file mode 100644 index 00000000..2661640e --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Storage; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.Connector; + +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +{ + /// + /// If the activity name is signin/tokenExchange, this middleware will attempt to + /// exchange the token, and deduplicate the incoming call, ensuring only one + /// exchange request is processed. + /// + /// + /// If a user is signed into multiple Teams clients, the Bot could receive a + /// "signin/tokenExchange" from each client. Each token exchange request for a + /// specific user login will have an identical Activity.Value.Id. + /// + /// Only one of these token exchange requests should be processed by the bot. + /// The others return . + /// For a distributed bot in production, this requires a distributed storage + /// ensuring only one token exchange is processed. This middleware supports + /// CosmosDb storage found in Microsoft.Bot.Builder.Azure, or MemoryStorage for + /// local development. IStorage's ETag implementation for token exchange activity + /// deduplication. + /// + internal class DedupeTokenExchange + { + private readonly OAuthSettings _settings; + private readonly IStorage _storage; + + public DedupeTokenExchange(OAuthSettings oauthSettings, IStorage storage) + { + _settings = oauthSettings; + _storage = storage; + } + + /// + /// Deduplicates exchange token requests. + /// + /// + /// + /// + /// + /// true to continue processing the turn. + public async Task DedupeAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (ShouldExchange(turnContext)) + { + // If the TokenExchange is NOT successful, the response will have already been sent by ExchangedTokenAsync + if (!await ExchangedTokenAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false)) + { + return false; + } + + // Only one token exchange should proceed from here. Deduplication is performed second because in the case + // of failure due to consent required, every caller needs to receive the + if (!await DeduplicatedTokenExchangeIdAsync(turnContext, _storage, cancellationToken).ConfigureAwait(false)) + { + // If the token is not exchangeable, do not process this activity further. + return false; + } + } + + return true; + } + + private static bool ShouldExchange(ITurnContext turnContext) + { + // Teams + if (string.Equals(Channels.Msteams, turnContext.Activity.ChannelId, StringComparison.OrdinalIgnoreCase) + && string.Equals(SignInConstants.TokenExchangeOperationName, turnContext.Activity.Name, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // SharePoint + if (string.Equals(Channels.M365, turnContext.Activity.ChannelId, StringComparison.OrdinalIgnoreCase) + && string.Equals(SignInConstants.SharePointTokenExchange, turnContext.Activity.Name, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + private async Task DeduplicatedTokenExchangeIdAsync(ITurnContext turnContext, IStorage storage, CancellationToken cancellationToken) + { + // Create a StoreItem with Etag of the unique 'signin/tokenExchange' request + var storeItem = new TokenStoreItem + { + ETag = ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value)["id"].ToString(), + }; + + var storeItems = new Dictionary { { TokenStoreItem.GetStorageKey(turnContext), storeItem } }; + try + { + // Writing the IStoreItem with ETag of unique id will succeed only once + await storage.WriteAsync(storeItems, cancellationToken).ConfigureAwait(false); + } + catch (EtagException) + { + // Do NOT proceed processing this message, some other thread or machine already has processed it. + + // Send 200 invoke response. + await SendInvokeResponseAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + return false; + } + + return true; + } + + private async Task SendInvokeResponseAsync(ITurnContext turnContext, object body = null, HttpStatusCode httpStatusCode = HttpStatusCode.OK, CancellationToken cancellationToken = default) + { + await turnContext.SendActivityAsync( + new Activity + { + Type = ActivityTypes.InvokeResponse, + Value = new InvokeResponse + { + Status = (int)httpStatusCode, + Body = body, + }, + }, cancellationToken).ConfigureAwait(false); + } + + private async Task ExchangedTokenAsync(ITurnContext turnContext, string connectionName, CancellationToken cancellationToken) + { + TokenResponse tokenExchangeResponse = null; + var tokenExchangeRequest = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + + try + { + var userTokenClient = turnContext.Services.Get(); + if (userTokenClient != null) + { + tokenExchangeResponse = await UserTokenClientWrapper.ExchangeTokenAsync( + turnContext, + connectionName, + new TokenExchangeRequest { Token = tokenExchangeRequest.Token }, + cancellationToken).ConfigureAwait(false); + } + else + { + throw new NotSupportedException("Token Exchange is not supported by the current adapter."); + } + } +#pragma warning disable CA1031 // Do not catch general exception types (ignoring, see comment below) + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + // Ignore Exceptions + // If token exchange failed for any reason, tokenExchangeResponse above stays null, + // and hence we send back a failure invoke response to the caller. + } + + if (string.IsNullOrEmpty(tokenExchangeResponse?.Token)) + { + // The token could not be exchanged (which could be due to a consent requirement) + // Notify the sender that PreconditionFailed so they can respond accordingly. + + var invokeResponse = new TokenExchangeInvokeResponse + { + Id = tokenExchangeRequest.Id, + ConnectionName = connectionName, + FailureDetail = "The bot is unable to exchange token. Proceed with regular login.", + }; + + await SendInvokeResponseAsync(turnContext, invokeResponse, HttpStatusCode.PreconditionFailed, cancellationToken).ConfigureAwait(false); + + return false; + } + + return true; + } + + private class TokenStoreItem : IStoreItem + { + public string ETag { get; set; } + + public static string GetStorageKey(ITurnContext turnContext) + { + var activity = turnContext.Activity; + var channelId = activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); + + var value = activity.Value.ToJsonElements(); + if (value == null || !value.ContainsKey("id")) + { + throw new InvalidOperationException("Invalid signin/tokenExchange. Missing activity.Value.Id."); + } + + return $"oauth/{channelId}/{conversationId}/{value["id"]}"; + } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs new file mode 100644 index 00000000..986b456f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Storage; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +{ + /// + /// Handles user authentication using The Azure Bot Token Service. + /// + public class OAuthAuthentication : IAuthentication + { + private readonly OAuthSettings _settings; + //private readonly OAuthMessageExtensionsAuthentication? _messageExtensionAuth; + private readonly OAuthBotAuthentication _botAuthentication; + + /// + /// Initializes the class + /// + /// The authentication name. + /// The settings to initialize the class + /// The storage to use. + public OAuthAuthentication(string name, OAuthSettings settings, IStorage storage) : this(settings, new OAuthBotAuthentication(name, settings, storage)) + { + } + + /// + /// Initializes the class + /// + /// The settings to initialize the class + /// The bot authentication instance + /// The message extension authentication instance + internal OAuthAuthentication(OAuthSettings settings, OAuthBotAuthentication botAuthentication) + { + _settings = settings; + //_messageExtensionAuth = messageExtensionAuth; + _botAuthentication = botAuthentication; + } + + /// + /// Check if the user is signed, if they are then return the token. + /// + /// The turn turnContext. + /// The cancellation token + /// The token if the user is signed. Otherwise null. + public async Task IsUserSignedInAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + TokenResponse tokenResponse = await GetUserToken(turnContext, _settings.ConnectionName, cancellationToken); + + if (!string.IsNullOrWhiteSpace(tokenResponse?.Token)) + { + return tokenResponse.Token; + } + + return null; + } + + /// + /// Sign in current user + /// + /// The turn turnContext + /// The cancellation token + /// The sign in response + public async Task SignInUserAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + /* + if ((_messageExtensionAuth != null && _messageExtensionAuth.IsValidActivity(turnContext))) + { + return await _messageExtensionAuth.AuthenticateAsync(turnContext).ConfigureAwait(false); + } + */ + + if (_botAuthentication != null && _botAuthentication.IsValidActivity(turnContext)) + { + return await _botAuthentication.AuthenticateAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + + throw new AuthException("Incoming activity is not a valid activity to initiate authentication flow.", AuthExceptionReason.InvalidActivity); + } + + public async Task ResetStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + await _botAuthentication.ResetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + + /// + /// Sign out current user + /// + /// The turn turnContext + /// The cancellation token + public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + await ResetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); + await UserTokenClientWrapper.SignOutUserAsync(turnContext, _settings.ConnectionName, cancellationToken); + } + + /// + /// Get user token + /// + protected virtual async Task GetUserToken(ITurnContext turnContext, string connectionName, CancellationToken cancellationToken) + { + return await UserTokenClientWrapper.GetUserTokenAsync(turnContext, connectionName, "", cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs new file mode 100644 index 00000000..4726f14a --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Storage; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +{ + /// + /// Base class for bot authentication that handles common logic. + /// + internal class OAuthBotAuthentication + { + private readonly OAuthFlow _flow; + private readonly OAuthSettings _settings; + private readonly IStorage _storage; + private FlowState _state; + private readonly DedupeTokenExchange _dedupe; + + /// + /// Name of the authentication handler + /// + protected string _name; + + /// + /// Initializes the class + /// + /// The name of authentication handler + /// + /// + public OAuthBotAuthentication(string name, OAuthSettings oauthSettings, IStorage storage) + { + _name = name; + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + _settings = oauthSettings ?? throw new ArgumentNullException(nameof(oauthSettings)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _dedupe = new DedupeTokenExchange(_settings, _storage); + + // Subclasses will define the signin prompt so the OAuthFlow optional args aren't needed. + _flow = new OAuthFlow(oauthSettings); + } + + /// + /// Whether the current activity is a valid activity that supports authentication + /// + /// The turn context + /// True if valid. Otherwise, false. + public virtual bool IsValidActivity(ITurnContext context) + { + // TODO: if flow hasn't started, does it matter what the Activity.Type is? Though it is likely always an Activity (until it's not). + var isMatch = context.Activity.Type == ActivityTypes.Message + && !string.IsNullOrEmpty(context.Activity.Text); + + // TODO: the following is only true if the flow is already started, but we don't know that yet. + isMatch |= context.Activity.Type == ActivityTypes.Invoke && + context.Activity.Name == SignInConstants.VerifyStateOperationName; + + isMatch |= context.Activity.Type == ActivityTypes.Invoke && + context.Activity.Name == SignInConstants.TokenExchangeOperationName; + + return isMatch; + } + + public virtual async Task ResetStateAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + await _storage.DeleteAsync([GetStorageKey(turnContext)], cancellationToken).ConfigureAwait(false); + } + + private async Task GetFlowStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + var key = GetStorageKey(turnContext); + var items = await _storage.ReadAsync([key], cancellationToken).ConfigureAwait(false); + return items.TryGetValue(key, out object value) ? (FlowState)value : new FlowState(); + } + + private async Task SaveFlowStateAsync(ITurnContext turnContext, FlowState state, CancellationToken cancellationToken) + { + var key = GetStorageKey(turnContext); + var items = new Dictionary() + { + { key, state } + }; + await _storage.WriteAsync(items, cancellationToken).ConfigureAwait(false); + } + + /// + /// Get a token for the user. + /// + /// The turn context + /// The cancellation token + /// The token response if available. + public async Task AuthenticateAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (_settings.EnableSso && !await _dedupe.DedupeAsync(turnContext, cancellationToken).ConfigureAwait(false)) + { + return null; + } + + _state = await GetFlowStateAsync(turnContext, cancellationToken).ConfigureAwait(false); + + TokenResponse tokenResponse; + if (!_state.FlowStarted) + { + // If the user is already signed in, tokenResponse will be non-null + tokenResponse = await OnGetOrStartFlowAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + else + { + // For non-Teams bots, the user sends the "magic code" that will be used to exchange for a token. + tokenResponse = await OnContinueFlow(turnContext, cancellationToken); + } + + await SaveFlowStateAsync(turnContext, _state, cancellationToken).ConfigureAwait(false); + + return tokenResponse; + } + + private async Task OnGetOrStartFlowAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + // If the user is already signed in, tokenResponse will be non-null + var tokenResponse = await _flow.BeginFlowAsync( + turnContext, + null, + cancellationToken).ConfigureAwait(false); + + // If a TokenResponse is returned, there was a cached token already. Otherwise, start the process of getting a new token. + if (tokenResponse == null) + { + var expires = DateTime.UtcNow.AddMilliseconds(_settings.Timeout ?? OAuthSettings.DefaultTimeoutValue.TotalMilliseconds); + + _state.FlowStarted = true; + _state.FlowExpires = expires; + } + + return tokenResponse; + } + + private async Task OnContinueFlow(ITurnContext turnContext, CancellationToken cancellationToken) + { + TokenResponse tokenResponse = null; + + _state.ContinueCount++; + + try + { + tokenResponse = await _flow.ContinueFlowAsync(turnContext, _state.FlowExpires, cancellationToken).ConfigureAwait(false); + } + catch (TimeoutException) + { + throw new AuthException("Authentication flow timed out.", AuthExceptionReason.Timeout); + } + + if (tokenResponse == null) + { + if (_state.ContinueCount >= _settings.InvalidSignInRetryMax) + { + // The only way this happens is if C2 sent a bogus code + throw new AuthException("Invalid sign in.", AuthExceptionReason.InvalidSignIn); + } + + await turnContext.SendActivityAsync(_settings.InvalidSignInRetryMessage, cancellationToken: cancellationToken).ConfigureAwait(false); + return null; + } + + _state.FlowStarted = false; + return tokenResponse; + } + + private string GetStorageKey(ITurnContext turnContext) + { + var channelId = turnContext.Activity.ChannelId ?? throw new InvalidOperationException("invalid activity-missing channelId"); + var conversationId = turnContext.Activity.Conversation?.Id ?? throw new InvalidOperationException("invalid activity-missing Conversation.Id"); + return $"oauth/{_name}/{channelId}/{conversationId}/flowState"; + } + } + + class FlowState + { + public bool FlowStarted = false; + public DateTime FlowExpires = DateTime.MinValue; + public int ContinueCount = 0; + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs similarity index 76% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs index 07eebd2a..fd9812f7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthFlow.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; @@ -11,10 +10,10 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService { /// - /// Creates a new prompt that asks the user to sign in using the UserTokenService Single Sign On (SSO) + /// Creates a new prompt that asks the user to sign in using the Azure Bot Token Service. /// service. /// /// @@ -31,29 +30,28 @@ namespace Microsoft.Agents.BotBuilder /// Both flows are automatically supported by the `OAuthFlow` and the only thing you need to be /// careful of is that you don't block the `event` and `invoke` activities that the prompt might /// be waiting on. - /// The title of the OAuthCard|SigninCard sent. - /// The title of the OAuthCard|SigninCard sent. - /// Required: The name of the OAuth connection to use. - /// Option login timeout. - /// Options, but true if the Channel requires it. + /// /// /// /// Initializes a new instance of the class. /// - public class OAuthFlow(string title, string text, string connectionName, int? timeout, bool? showSignInLink) + public class OAuthFlow { - public string Title { get; init; } = title ?? "Sign In"; - public string Text { get; init; } = text ?? "Please sign in"; - public string ConnectionName { get; init; } = connectionName ?? throw new ArgumentNullException(connectionName); - public int? Timeout { get; init; } = timeout; - public bool? ShowSignInLink { get; init; } = showSignInLink; + private readonly OAuthSettings _settings; - public virtual async Task BeginFlowAsync(ITurnContext turnContext, IActivity? prompt, CancellationToken cancellationToken = default) + public OAuthFlow(OAuthSettings settings) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public OAuthSettings Settings => _settings; + + public virtual async Task BeginFlowAsync(ITurnContext turnContext, Func>? promptFactory, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(turnContext); // Attempt to get the users token - var output = await GetTokenClient(turnContext).GetUserTokenAsync(turnContext.Activity.From.Id, ConnectionName, turnContext.Activity.ChannelId, magicCode: null, cancellationToken).ConfigureAwait(false); + var output = await UserTokenClientWrapper.GetUserTokenAsync(turnContext, _settings.ConnectionName, magicCode: null, cancellationToken).ConfigureAwait(false); if (output != null) { // Return token @@ -63,7 +61,7 @@ public virtual async Task BeginFlowAsync(ITurnContext turnContext // Prompt user to login await SendOAuthCardAsync( turnContext, - prompt, cancellationToken).ConfigureAwait(false); + promptFactory, cancellationToken).ConfigureAwait(false); return null; } @@ -81,7 +79,7 @@ await SendOAuthCardAsync( /// The prompt generally continues to receive the user's replies until it accepts the /// user's reply as valid input for the prompt. /// - public virtual async Task ContinueFlowAsync(ITurnContext turnContext, DateTime expires, CancellationToken cancellationToken = default) + public virtual async Task ContinueFlowAsync(ITurnContext turnContext, DateTime expires, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(turnContext); @@ -104,36 +102,9 @@ public virtual async Task ContinueFlowAsync(ITurnContext turnCont /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) - { - await GetTokenClient(turnContext).SignOutUserAsync(turnContext.Activity.From.Id, ConnectionName, turnContext.Activity.ChannelId, cancellationToken).ConfigureAwait(false); - } - - /// - /// Returns the IUserTokenClient for the OAuthFlow. - /// - /// - /// - /// - public static IUserTokenClient GetTokenClient(ITurnContext turnContext) + public async Task SignOutUserAsync(ITurnContext turnContext, CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(turnContext); - var userTokenClient = turnContext.Services.Get(); - if (userTokenClient != null) - { - return userTokenClient; - } - throw new NotSupportedException("OAuthFlow: IUserTokenClient is not supported. Was a IUserTokenClient registered?"); - } - - /// - /// Provide subclasses a chance to alter the SignInCard or other signin Activity values. - /// - /// - /// - protected virtual IActivity AlterSignInPrompt(IActivity prompt) - { - return prompt; + await UserTokenClientWrapper.SignOutUserAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false); } /// @@ -143,34 +114,48 @@ protected virtual IActivity AlterSignInPrompt(IActivity prompt) /// /// OAuthSettings. /// ITurnContext. - /// Activity. + /// Creates signin prompt /// CancellationToken. /// A representing the result of the asynchronous operation. - private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt, CancellationToken cancellationToken) + private async Task SendOAuthCardAsync(ITurnContext turnContext, Func>? promptFactory, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(turnContext); + IActivity prompt = null; + if (promptFactory != null) + { + prompt = await promptFactory().ConfigureAwait(false); + if (prompt != null && prompt.Attachments == null) + { + prompt.Attachments = []; + } + } + + if (prompt == null) + { + prompt = Activity.CreateMessageActivity(); + prompt.Attachments = []; + } + // Ensure prompt initialized - prompt ??= Activity.CreateMessageActivity(); - prompt.Attachments ??= []; // Append appropriate card if missing if (!ChannelSupportsOAuthCard(turnContext.Activity.ChannelId)) { if (!prompt.Attachments.Any(a => a.Content is SigninCard)) { - var signInResource = await GetTokenClient(turnContext).GetSignInResourceAsync(ConnectionName, turnContext.Activity, null, cancellationToken).ConfigureAwait(false); + var signInResource = await UserTokenClientWrapper.GetSignInResourceAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false); prompt.Attachments.Add(new Attachment { ContentType = SigninCard.ContentType, Content = new SigninCard { - Text = Text, + Text = _settings.Text, Buttons = [ new CardAction { - Title = Title, + Title = _settings.Title, Value = signInResource.SignInLink, Type = ActionTypes.Signin, }, @@ -182,11 +167,11 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt else if (!prompt.Attachments.Any(a => a.Content is OAuthCard)) { var cardActionType = ActionTypes.Signin; - var signInResource = await GetTokenClient(turnContext).GetSignInResourceAsync(ConnectionName, turnContext.Activity, null, cancellationToken).ConfigureAwait(false); + var signInResource = await UserTokenClientWrapper.GetSignInResourceAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false); string value; - if ((ShowSignInLink != null && ShowSignInLink == false) || - (ShowSignInLink == null && !ChannelRequiresSignInLink(turnContext.Activity.ChannelId))) + if (_settings.ShowSignInLink != null && _settings.ShowSignInLink == false || + _settings.ShowSignInLink == null && !ChannelRequiresSignInLink(turnContext.Activity.ChannelId)) { value = null; } @@ -195,24 +180,30 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt value = signInResource.SignInLink; } + TokenExchangeResource? tokenExchangeResource = null; + if (_settings.EnableSso == true) + { + tokenExchangeResource = signInResource.TokenExchangeResource; + } + prompt.Attachments.Add(new Attachment { ContentType = OAuthCard.ContentType, Content = new OAuthCard { - Text = Text, - ConnectionName = ConnectionName, + Text = _settings.Text, + ConnectionName = _settings.ConnectionName, Buttons = [ new CardAction { - Title = Title, - Text = Text, + Title = _settings.Title, + Text = _settings.Text, Type = cardActionType, Value = value }, ], - TokenExchangeResource = signInResource.TokenExchangeResource, + TokenExchangeResource = tokenExchangeResource, TokenPostResource = signInResource.TokenPostResource }, }); @@ -224,7 +215,7 @@ private async Task SendOAuthCardAsync(ITurnContext turnContext, IActivity prompt prompt.InputHint = InputHints.AcceptingInput; } - await turnContext.SendActivityAsync(AlterSignInPrompt(prompt), cancellationToken).ConfigureAwait(false); + await turnContext.SendActivityAsync(prompt, cancellationToken).ConfigureAwait(false); } /// @@ -246,8 +237,8 @@ private async Task RecognizeTokenAsync(ITurnContext turnContext, } else if (IsVerificationInvoke(turnContext)) { - var value = ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value); - var magicCode = value.ContainsKey("state") ? ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value)["state"].ToString() : null; + var value = turnContext.Activity.Value.ToJsonElements(); + var magicCode = value.ContainsKey("state") ? turnContext.Activity.Value.ToJsonElements()["state"].ToString() : null; // Getting the token follows a different flow in Teams. At the signin completion, Teams // will send the bot an "invoke" activity that contains a "magic" code. This code MUST @@ -258,7 +249,7 @@ private async Task RecognizeTokenAsync(ITurnContext turnContext, // progress) retry in that case. try { - result = await GetTokenClient(turnContext).GetUserTokenAsync(turnContext.Activity.From.Id, ConnectionName, turnContext.Activity.ChannelId, magicCode, cancellationToken).ConfigureAwait(false); + result = await UserTokenClientWrapper.GetUserTokenAsync(turnContext, _settings.ConnectionName, magicCode, cancellationToken).ConfigureAwait(false); if (result != null) { @@ -288,11 +279,11 @@ await SendInvokeResponseAsync( new TokenExchangeInvokeResponse { Id = null, - ConnectionName = ConnectionName, + ConnectionName = _settings.ConnectionName, FailureDetail = "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value. This is required to be sent with the InvokeActivity.", }, cancellationToken).ConfigureAwait(false); } - else if (tokenExchangeRequest.ConnectionName != ConnectionName) + else if (tokenExchangeRequest.ConnectionName != _settings.ConnectionName) { await SendInvokeResponseAsync( turnContext, @@ -300,7 +291,7 @@ await SendInvokeResponseAsync( new TokenExchangeInvokeResponse { Id = tokenExchangeRequest.Id, - ConnectionName = ConnectionName, + ConnectionName = _settings.ConnectionName, FailureDetail = "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a ConnectionName that does not match the ConnectionName expected by the bot's active OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid ConnectionName in the TokenExchangeInvokeRequest", }, cancellationToken).ConfigureAwait(false); } @@ -312,7 +303,7 @@ await SendInvokeResponseAsync( var userId = turnContext.Activity.From.Id; var channelId = turnContext.Activity.ChannelId; var exchangeRequest = new TokenExchangeRequest { Token = tokenExchangeRequest.Token }; - tokenExchangeResponse = await GetTokenClient(turnContext).ExchangeTokenAsync(userId, ConnectionName, channelId, exchangeRequest, cancellationToken).ConfigureAwait(false); + tokenExchangeResponse = await UserTokenClientWrapper.ExchangeTokenAsync(turnContext, _settings.ConnectionName, exchangeRequest, cancellationToken).ConfigureAwait(false); } #pragma warning disable CA1031 // Do not catch general exception types (ignoring, see comment below) catch @@ -331,7 +322,7 @@ await SendInvokeResponseAsync( new TokenExchangeInvokeResponse { Id = tokenExchangeRequest.Id, - ConnectionName = ConnectionName, + ConnectionName = _settings.ConnectionName, FailureDetail = "The bot is unable to exchange token. Proceed with regular login.", }, cancellationToken).ConfigureAwait(false); } @@ -343,7 +334,7 @@ await SendInvokeResponseAsync( new TokenExchangeInvokeResponse { Id = tokenExchangeRequest.Id, - ConnectionName = ConnectionName, + ConnectionName = _settings.ConnectionName, }, cancellationToken).ConfigureAwait(false); result = new TokenResponse @@ -364,11 +355,12 @@ await SendInvokeResponseAsync( var matched = magicCodeRegex.Match(turnContext.Activity.Text); if (matched.Success) { - result = await GetTokenClient(turnContext).GetUserTokenAsync( - turnContext.Activity.From.Id, - ConnectionName, - turnContext.Activity.ChannelId, - magicCode: matched.Value, + // Note that if result is null, it is likely because the magicCode was invalid. + // The Token Service doesn't provide any way to determine this though. + result = await UserTokenClientWrapper.GetUserTokenAsync( + turnContext, + _settings.ConnectionName, + magicCode: matched.Value, cancellationToken).ConfigureAwait(false); } } @@ -404,7 +396,7 @@ private static bool ChannelSupportsOAuthCard(string channelId) }; } - private static bool ChannelRequiresSignInLink(string channelId) + public static bool ChannelRequiresSignInLink(string channelId) { return channelId switch { @@ -413,7 +405,6 @@ private static bool ChannelRequiresSignInLink(string channelId) }; } - private static bool HasTimedOut(ITurnContext context, DateTime expires) { var isMessage = context.Activity.Type == ActivityTypes.Message; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs new file mode 100644 index 00000000..20d5b3a3 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +{ + /// + /// The settings for OAuthAuthentication. + /// + public class OAuthSettings + { + /// + /// The default timeout for the exchange. + /// + public static readonly TimeSpan DefaultTimeoutValue = TimeSpan.FromMinutes(15); + + /// + /// Gets or sets the name of the OAuth connection. + /// + /// The name of the OAuth connection. + public string ConnectionName { get; set; } + + /// + /// Gets or sets the title of the sign-in card. + /// + /// The title of the sign-in card. + public string Title { get; set; } = "Sign In"; + + /// + /// Gets or sets any additional text to include in the sign-in card. + /// + /// Any additional text to include in the sign-in card. + public string Text { get; set; } = "Please sign in"; + + public string InvalidSignInRetryMessage { get; set; } = "Invalid sign in. Please try again."; + public int InvalidSignInRetryMax { get; set; } = 2; + + /// + /// Gets or sets the number of milliseconds the prompt waits for the user to authenticate. + /// Default is . + /// + /// The number of milliseconds the prompt waits for the user to authenticate. + public int? Timeout { get; set; } = (int) DefaultTimeoutValue.TotalMilliseconds; + + /// + /// Gets or sets a value indicating whether the should end upon + /// receiving an invalid message. Generally the will ignore + /// incoming messages from the user during the auth flow, if they are not related to the + /// auth flow. This flag enables ending the rather than + /// ignoring the user's message. Typically, this flag will be set to 'true', but is 'false' + /// by default for backwards compatibility. + /// + /// True if the should automatically end upon receiving + /// an invalid message. + public bool EndOnInvalidMessage { get; set; } + + /// + /// Gets or sets an optional boolean value to force the display of a Sign In link overriding + /// the default behavior. + /// + /// True to display the SignInLink. + public bool? ShowSignInLink { get; set; } + + /// + /// Set to `true` to enable SSO when authenticating using Azure Active Directory (AAD). + /// + public bool EnableSso { get; set; } = true; + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs new file mode 100644 index 00000000..ccaba06f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.Connector; +using Microsoft.Agents.Core.Models; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +{ + public class UserTokenClientWrapper + { + public static async Task GetSignInResourceAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) + { + IUserTokenClient userTokenClient = GetUserTokenClient(context); + return await userTokenClient.GetSignInResourceAsync(connectionName, context.Activity, null, cancellationToken); + } + + public static async Task GetUserTokenAsync(ITurnContext context, string connectionName, string magicCode, CancellationToken cancellationToken) + { + IUserTokenClient userTokenClient = GetUserTokenClient(context); + return await userTokenClient.GetUserTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, magicCode, cancellationToken); + } + + public static async Task ExchangeTokenAsync(ITurnContext context, string connectionName, TokenExchangeRequest tokenExchangeRequest, CancellationToken cancellationToken) + { + IUserTokenClient userTokenClient = GetUserTokenClient(context); + return await userTokenClient.ExchangeTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, tokenExchangeRequest, cancellationToken); + } + + public static async Task SignOutUserAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) + { + IUserTokenClient userTokenClient = GetUserTokenClient(context); + await userTokenClient.SignOutUserAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, cancellationToken); + } + + private static IUserTokenClient GetUserTokenClient(ITurnContext context) + { + IUserTokenClient userTokenClient = context.Services.Get(); + if (userTokenClient == null) + { + throw new NotSupportedException("IUserTokenClient is not supported by the current adapter"); + } + return userTokenClient; + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs index bf3376c7..adccdf5b 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/StreamingResponse.cs @@ -4,6 +4,7 @@ using Microsoft.Agents.Core.Models; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.BotBuilder.App @@ -311,7 +312,7 @@ private async Task SendActivity(Activity activity) } */ - ResourceResponse response = await this._context.SendActivityAsync(activity).ConfigureAwait(false); + ResourceResponse response = await this._context.SendActivityAsync(activity, CancellationToken.None).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1.5)); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index 97e7c0bb..fe01cfc7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -21,7 +21,7 @@ internal class TypingTimer : IDisposable private readonly int _interval; // For synchronizing SendActivity and Typing to prevent race - private static Mutex _send; + private static AutoResetEvent _send; /// /// Initial delay before first typing is sent. @@ -65,7 +65,7 @@ public bool Start(ITurnContext turnContext) // Stop timer when message activities are sent turnContext.OnSendActivities(StopTimerWhenSendMessageActivityHandlerAsync); - _send = new Mutex(false); + _send = new AutoResetEvent(false); // Start periodically send "typing" activity _timer = new Timer(SendTypingActivity, turnContext, Timeout.Infinite, Timeout.Infinite); @@ -117,13 +117,13 @@ private async void SendTypingActivity(object state) try { - _send.WaitOne(); if (_timer != null) { - await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing, RelatesTo = turnContext.Activity.RelatesTo, Text = "TYPING" }); + await turnContext.SendActivityAsync(new Activity { Type = ActivityTypes.Typing, RelatesTo = turnContext.Activity.RelatesTo, Text = "TYPING" }, CancellationToken.None).ConfigureAwait(false); if (IsRunning()) { _timer?.Change(_interval, Timeout.Infinite); + _send.WaitOne(); } } } @@ -134,10 +134,6 @@ private async void SendTypingActivity(object state) // error but lets make sure our states cleaned up a bit. Dispose(); } - finally - { - _send.ReleaseMutex(); - } } private Task StopTimerWhenSendMessageActivityHandlerAsync(ITurnContext turnContext, List activities, Func> next) @@ -149,13 +145,11 @@ private Task StopTimerWhenSendMessageActivityHandlerAsync(IT if (activity.Type == ActivityTypes.Message) { // This will block ITurnContext.SendActivity until the typing timer is done. - _send.WaitOne(); + _send.Set(); // Stop timer Dispose(); - // Release, which could free up timer SendTypingActivity (if it was blocked). - _send.ReleaseMutex(); break; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs index f4d5750e..15f6e82f 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelAdapter.cs @@ -104,6 +104,16 @@ public virtual Task ProcessActivityAsync(ClaimsIdentity claimsId throw new NotImplementedException(); } + public virtual Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public virtual Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, IBot bot, CancellationToken cancellationToken, string audience = null) + { + throw new NotImplementedException(); + } + public virtual Task UpdateActivityAsync(ITurnContext turnContext, IActivity activity, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs index 69e2707f..06e36fa7 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ChannelServiceAdapterBase.cs @@ -169,7 +169,7 @@ public override Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, IA _ = callback ?? throw new ArgumentNullException(nameof(callback)); ValidateContinuationActivity(continuationActivity); - return ProcessProactiveAsync(claimsIdentity, continuationActivity, null, callback, cancellationToken); + return ProcessProactiveAsync(claimsIdentity, continuationActivity, BotClaims.GetTokenAudience(claimsIdentity), callback, cancellationToken); } /// @@ -219,6 +219,11 @@ public override async Task CreateConversationAsync(string botAppId, string chann } } + public override async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, IBot bot, CancellationToken cancellationToken, string audience = null) + { + await ProcessProactiveAsync(claimsIdentity, continuationActivity, audience, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); + } + /// /// The implementation for continue conversation. /// @@ -227,8 +232,9 @@ public override async Task CreateConversationAsync(string botAppId, string chann /// The audience for the call. /// The method to call for the resulting bot turn. /// Cancellation token. + /// /// A task that represents the work queued to execute. - protected async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken) + public override async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken) { Logger.LogInformation($"ProcessProactiveAsync for Conversation Id: {continuationActivity.Conversation.Id}"); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs index 90adc934..8b7fa22c 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/AutoSaveStateMiddleware.cs @@ -71,13 +71,13 @@ public AutoSaveStateMiddleware(ITurnState botStateSet) // before turn if (_autoLoad) { - await TurnState.LoadStateAsync(turnContext, true, cancellationToken).ConfigureAwait(false); + await TurnState.LoadStateAsync(turnContext, cancellationToken:cancellationToken, force:true).ConfigureAwait(false); } await next(cancellationToken).ConfigureAwait(false); // after turn - await TurnState.SaveStateAsync(turnContext, false, cancellationToken).ConfigureAwait(false); + await TurnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs index 2d191925..7c384516 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/IChannelAdapter.cs @@ -202,6 +202,19 @@ public interface IChannelAdapter /// A task that represents the work queued to execute. Task ProcessActivityAsync(ClaimsIdentity claimsIdentity, IActivity activity, BotCallbackHandler callback, CancellationToken cancellationToken); + /// + /// The implementation for continue conversation. + /// + /// A for the conversation. + /// The continuation used to create the . + /// The audience for the call. + /// The method to call for the resulting bot turn. + /// Cancellation token. + /// For Adapters that support async Activity processing, this allows control to force synchronous handling. + /// A task that represents the work queued to execute. + Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken); + Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, IBot bot, CancellationToken cancellationToken, string audience = null); + /// /// When overridden in a derived class, sends activities to the conversation. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs deleted file mode 100644 index 4d8c8d85..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/OAuthTurnStateConstants.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.Agents.BotBuilder -{ - /// - /// Constants used in TurnState. - /// - public static class OAuthTurnStateConstants - { - /// - /// Default amount of time an OAuthCard will remain active (clickable and actively waiting for a token). - /// After this time: - /// (1) the OAuthCard will not allow the user to click on it. - /// (2) any polling triggered by the OAuthCard will stop. - /// - public static readonly TimeSpan OAuthLoginTimeoutValue = TimeSpan.FromMinutes(15); - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs index ab4c1a64..bd70677b 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/BotState.cs @@ -117,6 +117,24 @@ public T GetValue(string name, Func defaultValueFactory = null) return result; } + public bool TryGetValue(string name, out T result) + { + if (!IsLoaded()) + { + result = default; + return false; + } + + if (!HasValue(name)) + { + result = default; + return false; + } + + result = GetPropertyValue(name); + return true; + } + /// public void SetValue(string name, T value) { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs index 33cc9e25..681f6ccc 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/State/TempState.cs @@ -16,6 +16,11 @@ public class TempState : IBotState /// public const string InputFilesKey = "inputFiles"; + /// + /// Name of the auth tokens property. + /// + public const string AuthTokenKey = "authTokens"; + public static readonly string ScopeName = "temp"; private readonly Dictionary _state = []; @@ -29,6 +34,14 @@ public IList InputFiles get => GetValue>(InputFilesKey, () => []); set => SetValue(InputFilesKey, value); } + /// + /// All tokens acquired after sign-in for current activity + /// + public Dictionary AuthTokens + { + get => GetValue>(AuthTokenKey, () => []); + set => SetValue(AuthTokenKey, value); + } public void ClearState() { diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs index f329bbc3..516cce22 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/UserTokenRestClient.cs @@ -98,6 +98,7 @@ public async Task GetTokenAsync(string userId, string connectionN case 200: return ProtocolJsonSerializer.ToObject(httpResponse.Content.ReadAsStream(cancellationToken)); case 404: + // there isn't a body provided in this case. This can happen when the code is invalid. return null; default: throw new HttpRequestException($"GetTokenAsync {httpResponse.StatusCode}"); diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs index 52ad3e10..857f66de 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Serialization/ProtocolJsonSerializer.cs @@ -159,7 +159,13 @@ public static T ToObject(object value, Func defaultFactory = null) return System.Text.Json.JsonSerializer.Deserialize(serialized, SerializationOptions); } - + public static bool Equals(T value1, T value2) + { + return string.Equals( + JsonSerializer.Serialize(value1, ProtocolJsonSerializer.SerializationOptions), + JsonSerializer.Serialize(value2, ProtocolJsonSerializer.SerializationOptions) + ); + } public static T CloneTo(object obj) { diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index 1b670ff7..d6c15f81 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -4,6 +4,7 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs index 8e62c462..62dbaafe 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs @@ -3,6 +3,7 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs index f3e3621b..83d48a02 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs @@ -14,6 +14,7 @@ using Microsoft.Agents.Extensions.Teams.App.Meetings; using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; using Microsoft.Agents.Extensions.Teams.App.TaskModules; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; namespace Microsoft.Agents.Extensions.Teams.App { diff --git a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityTaskQueue.cs b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityTaskQueue.cs index c0305f49..9d2cae99 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityTaskQueue.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityTaskQueue.cs @@ -18,38 +18,23 @@ public class ActivityTaskQueue : IActivityTaskQueue private readonly SemaphoreSlim _signal = new(0); private readonly ConcurrentQueue _activities = new ConcurrentQueue(); - /// - /// Enqueue an Activity, with Claims, to be processed on a background thread. - /// - /// - /// It is assumed these Claims have been authenticated via JwtTokenValidation.AuthenticateRequest - /// before enqueueing. - /// - /// Authenticated used to process the - /// activity. - /// to be processed. - public void QueueBackgroundActivity(ClaimsIdentity claimsIdentity, Activity activity) + + /// + public void QueueBackgroundActivity(ClaimsIdentity claimsIdentity, IActivity activity, bool proactive = false, string proactiveAudience = null, Type bot = null) { ArgumentNullException.ThrowIfNull(claimsIdentity); ArgumentNullException.ThrowIfNull(activity); - _activities.Enqueue(new ActivityWithClaims { ClaimsIdentity = claimsIdentity, Activity = activity}); + _activities.Enqueue(new ActivityWithClaims { BotType = bot, ClaimsIdentity = claimsIdentity, Activity = activity, IsProactive = proactive, ProactiveAudience = proactiveAudience }); _signal.Release(); } - /// - /// Wait for a signal of an enqueued Activity with Claims to be processed. - /// - /// CancellationToken used to cancel the wait. - /// An ActivityWithClaims to be processed. - /// - /// It is assumed these claims have already been authenticated via JwtTokenValidation.AuthenticateRequest. + /// public async Task WaitForActivityAsync(CancellationToken cancellationToken) { await _signal.WaitAsync(cancellationToken); - ActivityWithClaims dequeued; - _activities.TryDequeue(out dequeued); + _activities.TryDequeue(out ActivityWithClaims dequeued); return dequeued; } diff --git a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityWithClaims.cs b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityWithClaims.cs index f3a10a2a..4047633b 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityWithClaims.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/ActivityWithClaims.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. // using Microsoft.Agents.Core.Models; +using System; using System.Security.Claims; namespace Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue @@ -11,6 +12,11 @@ namespace Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue /// public class ActivityWithClaims { + /// + /// Optional: Defaults to IBot + /// + public Type BotType { get; set; } + /// /// retrieved from a call to authentication. /// @@ -19,6 +25,10 @@ public class ActivityWithClaims /// /// to be processed. /// - public Activity Activity { get; set; } + public IActivity Activity { get; set; } + + public bool IsProactive { get; set; } + public string ProactiveAudience { get; set; } + } } diff --git a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs index e36adb7c..38f62bb8 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/HostedActivityService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Agents.Authentication; using Microsoft.Agents.BotBuilder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -25,8 +26,8 @@ public class HostedActivityService : BackgroundService private readonly ConcurrentDictionary _activitiesProcessing = new ConcurrentDictionary(); private IActivityTaskQueue _activityQueue; private readonly IChannelAdapter _adapter; - private readonly IBot _bot; private readonly int _shutdownTimeoutSeconds; + private readonly IServiceProvider _serviceProvider; /// /// Create a instance for processing Activities @@ -35,24 +36,25 @@ public class HostedActivityService : BackgroundService /// /// It is important to note that exceptions on the background thread are only logged in the . /// + /// /// used to retrieve ShutdownTimeoutSeconds from appsettings. - /// IBot which will be used to process Activities. /// used to process Activities. /// Queue of activities to be processed. This class /// contains a semaphore which the BackgroundService waits on to be notified of activities to be processed. /// Logger to use for logging BackgroundService processing and exception information. - public HostedActivityService(IConfiguration config, IBot bot, IChannelAdapter adapter, IActivityTaskQueue activityTaskQueue, ILogger logger) + /// + public HostedActivityService(IServiceProvider provider, IConfiguration config, IChannelAdapter adapter, IActivityTaskQueue activityTaskQueue, ILogger logger, AdapterOptions options = null) { ArgumentNullException.ThrowIfNull(config); - ArgumentNullException.ThrowIfNull(bot); ArgumentNullException.ThrowIfNull(adapter); ArgumentNullException.ThrowIfNull(activityTaskQueue); + ArgumentNullException.ThrowIfNull(provider); - _shutdownTimeoutSeconds = config.GetValue("ShutdownTimeoutSeconds", 60); + _shutdownTimeoutSeconds = options != null ? options.ShutdownTimeoutSeconds : 60; _activityQueue = activityTaskQueue; - _bot = bot; _adapter = adapter; _logger = logger ?? NullLogger.Instance; + _serviceProvider = provider; } /// @@ -128,7 +130,28 @@ private Task GetTaskFromWorkItem(ActivityWithClaims activityWithClaims, Cancella { try { - await _adapter.ProcessActivityAsync(activityWithClaims.ClaimsIdentity, activityWithClaims.Activity, _bot.OnTurnAsync, stoppingToken); + // We must go back through DI to get the IBot. This is because the IBot is typically transient, and anything + // else that is transient as part of the bot, that uses IServiceProvider will encounter error since that is scoped + // and disposed before this gets called. + var bot = _serviceProvider.GetService(activityWithClaims.BotType ?? typeof(IBot)); + + if (activityWithClaims.IsProactive) + { + await _adapter.ProcessProactiveAsync( + activityWithClaims.ClaimsIdentity, + activityWithClaims.Activity, + activityWithClaims.ProactiveAudience ?? BotClaims.GetTokenAudience(activityWithClaims.ClaimsIdentity), + ((IBot)bot).OnTurnAsync, + stoppingToken).ConfigureAwait(false); + } + else + { + await _adapter.ProcessActivityAsync( + activityWithClaims.ClaimsIdentity, + activityWithClaims.Activity, + ((IBot)bot).OnTurnAsync, + stoppingToken).ConfigureAwait(false); + } } catch (Exception ex) { diff --git a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/IActivityTaskQueue.cs b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/IActivityTaskQueue.cs index fa8e02bc..df400ce9 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundActivityService/IActivityTaskQueue.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundActivityService/IActivityTaskQueue.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. // using Microsoft.Agents.Core.Models; +using System; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -21,7 +22,10 @@ public interface IActivityTaskQueue /// Authenticated used to process the /// activity. /// to be processed. - void QueueBackgroundActivity(ClaimsIdentity claimsIdentity, Activity activity); + /// + /// + /// + void QueueBackgroundActivity(ClaimsIdentity claimsIdentity, IActivity activity, bool proactive = false, string proactiveAudience = null, Type bot = null); /// /// Wait for a signal of an enqueued Activity with Claims to be processed. diff --git a/src/libraries/Hosting/AspNetCore/BackgroundTaskService/HostedTaskService.cs b/src/libraries/Hosting/AspNetCore/BackgroundTaskService/HostedTaskService.cs index e46ce447..45f0c41f 100644 --- a/src/libraries/Hosting/AspNetCore/BackgroundTaskService/HostedTaskService.cs +++ b/src/libraries/Hosting/AspNetCore/BackgroundTaskService/HostedTaskService.cs @@ -20,7 +20,7 @@ public class HostedTaskService : BackgroundService { private readonly ILogger _logger; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly ConcurrentDictionary, Task> _tasks = new ConcurrentDictionary, Task>(); + private readonly ConcurrentDictionary, Task> _tasks = new(); private readonly IBackgroundTaskQueue _taskQueue; private readonly int _shutdownTimeoutSeconds; diff --git a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs index c781166c..61d71cd9 100644 --- a/src/libraries/Hosting/AspNetCore/CloudAdapter.cs +++ b/src/libraries/Hosting/AspNetCore/CloudAdapter.cs @@ -70,12 +70,12 @@ public CloudAdapter( } string resolvedErrorMessage = sbError.ToString(); - // Writing formatted exception message to log with errocodes and help links. + // Writing formatted exception message to log with error codes and help links. logger.LogError(resolvedErrorMessage); if (exception is not OperationCanceledException) // Do not try to send another message if the response has been canceled. { - await turnContext.SendActivityAsync(MessageFactory.Text(resolvedErrorMessage)); + await turnContext.SendActivityAsync(MessageFactory.Text(resolvedErrorMessage), CancellationToken.None); // Send a trace activity await turnContext.TraceActivityAsync("OnTurnError Trace", resolvedErrorMessage, "https://www.botframework.com/schemas/error", "TurnError"); } @@ -111,7 +111,7 @@ public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpRespons else { // Deserialize the incoming Activity - var activity = await HttpHelper.ReadRequestAsync(httpRequest).ConfigureAwait(false); + var activity = await HttpHelper.ReadRequestAsync(httpRequest).ConfigureAwait(false); var claimsIdentity = (ClaimsIdentity)httpRequest.HttpContext.User.Identity; if (!IsValidChannelActivity(activity, httpResponse)) @@ -148,7 +148,28 @@ public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpRespons } } - private bool IsValidChannelActivity(Activity activity, HttpResponse httpResponse) + /// + /// CloudAdapter handles this override asynchronously. + /// + /// + /// + /// + /// + /// + /// + public override Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, IActivity continuationActivity, IBot bot, CancellationToken cancellationToken, string audience = null) + { + if (_adapterOptions.Async) + { + // Queue the activity to be processed by the ActivityBackgroundService + _activityTaskQueue.QueueBackgroundActivity(claimsIdentity, continuationActivity, proactive: true, proactiveAudience: audience); + return Task.CompletedTask; + } + + return base.ProcessProactiveAsync(claimsIdentity, continuationActivity, bot, cancellationToken, audience); + } + + private bool IsValidChannelActivity(IActivity activity, HttpResponse httpResponse) { if (activity == null) { diff --git a/src/libraries/Storage/Microsoft.Agents.Storage.Blobs/BlobsStorage.cs b/src/libraries/Storage/Microsoft.Agents.Storage.Blobs/BlobsStorage.cs index e69022c7..1a386675 100644 --- a/src/libraries/Storage/Microsoft.Agents.Storage.Blobs/BlobsStorage.cs +++ b/src/libraries/Storage/Microsoft.Agents.Storage.Blobs/BlobsStorage.cs @@ -236,7 +236,7 @@ public async Task WriteAsync(IDictionary changes, CancellationTo catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.PreconditionFailed) { - throw new InvalidOperationException($"Etag conflict: {ex.Message}"); + throw new EtagException($"Etag conflict: {ex.Message}"); } } } diff --git a/src/libraries/Storage/Microsoft.Agents.Storage/EtagException.cs b/src/libraries/Storage/Microsoft.Agents.Storage/EtagException.cs new file mode 100644 index 00000000..682a55d6 --- /dev/null +++ b/src/libraries/Storage/Microsoft.Agents.Storage/EtagException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Agents.Storage +{ + public class EtagException : Exception + { + public EtagException() + { + } + + public EtagException(string message) : base(message) + { + } + + public EtagException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/libraries/Storage/Microsoft.Agents.Storage/MemoryStorage.cs b/src/libraries/Storage/Microsoft.Agents.Storage/MemoryStorage.cs index 7e69b450..6f895d18 100644 --- a/src/libraries/Storage/Microsoft.Agents.Storage/MemoryStorage.cs +++ b/src/libraries/Storage/Microsoft.Agents.Storage/MemoryStorage.cs @@ -144,7 +144,7 @@ public Task WriteAsync(IDictionary changes, CancellationToken ca && newStoreItem.ETag != oldStateETag) { - throw new ArgumentException($"Etag conflict.\r\n\r\nOriginal: {newStoreItem.ETag}\r\nCurrent: {oldStateETag}"); + throw new EtagException($"Etag conflict.\r\n\r\nOriginal: {newStoreItem.ETag}\r\nCurrent: {oldStateETag}"); } newState["ETag"] = (_eTag++).ToString(CultureInfo.InvariantCulture); diff --git a/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj b/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj new file mode 100644 index 00000000..a3033d63 --- /dev/null +++ b/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + latest + + + + + + + + + + Always + + + diff --git a/src/samples/Application/AuthenticationBot/BotController.cs b/src/samples/Application/AuthenticationBot/BotController.cs new file mode 100644 index 00000000..9c054590 --- /dev/null +++ b/src/samples/Application/AuthenticationBot/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using Microsoft.Agents.BotBuilder; + +namespace AuthenticationBot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/Application/AuthenticationBot/Program.cs b/src/samples/Application/AuthenticationBot/Program.cs new file mode 100644 index 00000000..7f6377c2 --- /dev/null +++ b/src/samples/Application/AuthenticationBot/Program.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AuthenticationBot; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.Authentication; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); + +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Create the bot as a transient. +builder.Services.AddTransient(sp => new TurnState(sp.GetService())); +builder.AddBot((sp) => +{ + var adapter = sp.GetService(); + + var authOptions = new AuthenticationOptions() + { + Storage = sp.GetService() + }; + authOptions.AddAuthentication("graph", new OAuthSettings() + { + ConnectionName = builder.Configuration["ConnectionName"], + Title = "Sign In", + Text = "Please sign in to use the bot.", + EndOnInvalidMessage = true, + EnableSso = true, + }); + + var appOptions = new ApplicationOptions() + { + Adapter = adapter, + StartTypingTimer = false, + Authentication = authOptions, + TurnStateFactory = () => sp.GetService() + }; + + var app = new Application(appOptions); + + // Listen for user to say "/reset" and then delete state + app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnState.User.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); + }); + + // Listen for user to say "/sigout" and then delete cached token + app.OnMessage("/signout", async (turnContext, turnState, cancellationToken) => + { + await app.Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); + await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); + }); + + // Display a welcome message + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Type anything to start sign in."), cancellationToken); + } + } + }); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => + { + int count = turnState.Conversation.IncrementMessageCount(); + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + }); + + return app; +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} + +app.Run(); + diff --git a/src/samples/Application/AuthenticationBot/README.md b/src/samples/Application/AuthenticationBot/README.md new file mode 100644 index 00000000..58acf9ca --- /dev/null +++ b/src/samples/Application/AuthenticationBot/README.md @@ -0,0 +1,97 @@ +# OAuth Authentication + +This Agent has been created using [Microsoft 365 Agents Framework](https://github.com/microsoft/agents-for-net), it shows how to use authentication in your Agent using OAuth. + +- The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. +- The samples demonstrates performing OAuth without using the Dialogs package. + +- ## Prerequisites + +- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 +- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) + +## Running this sample + +1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot) + - Record the Application ID, the Tenant ID, and the Client Secret for use below + +1. [Add OAuth to your bot](https://aka.ms/AgentsSDK-AddAuth) + +1. Configuring the token connection in the Agent settings + > The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) + + 1. Open the `appsettings.json` file in the root of the sample project. + + 1. Find the section labeled `Connections`, it should appear similar to this: + + ```json + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Assembly": "Microsoft.Agents.Authentication.Msal", + "Type": "MsalAuth", + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ], + "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. + } + } + ``` + + 1. Set the **ClientId** to the AppId of the bot identity. + 1. Set the **ClientSecret** to the Secret that was created for your identity. + 1. Set the **TenantId** to the Tenant Id where your application is registered. + 1. Set the **Audience** to the AppId of the bot identity. + + > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. + +1. Update `appsettings.json` + + | Property | Value Description | + |----------------------|-----------| + | ConnectionName | Set the configured bot's OAuth connection name. | + +1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and update it to match your DevTunnel port. If `./Properties/launchSettings.json`not fount Close and re-open the solution.launchSettings.json have been re-created. + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + +1. Update your Azure Bot ``Messaging endpoint`` with the tunnel Url: `{tunnel-url}/api/messages` + +1. Run the bot from a terminal or from Visual Studio + +1. Test via "Test in WebChat"" on your Azure Bot in the Azure Portal. + +## Running this Agent in Teams + +1. Manually update the manifest.json + - Edit the `manifest.json` contained in the `/appManifest` folder + - Replace with your AppId (that was created above) *everywhere* you see the place holder string `<>` + - Replace `<>` with your Agent url. For example, the tunnel host name. + - Zip up the contents of the `/appManifest` folder to create a `manifest.zip` +1. Upload the `manifest.zip` to Teams + - Select **Developer Portal** in the Teams left sidebar + - Select **Apps** (top row) + - Select **Import app**, and select the manifest.zip + +1. Select **Preview in Teams** in the upper right corner + +## Interacting with the Agent + +Type anything to sign-in, or `logout` to sign-out. + +## Further reading +To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. + diff --git a/src/samples/Application/AuthenticationBot/StateExtensions.cs b/src/samples/Application/AuthenticationBot/StateExtensions.cs new file mode 100644 index 00000000..7c0ab2db --- /dev/null +++ b/src/samples/Application/AuthenticationBot/StateExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.State; + +namespace AuthenticationBot +{ + public static class StateExtensions + { + public static int MessageCount(this ConversationState state) + { + return state.GetValue("countKey"); + } + + public static void MessageCount(this ConversationState state, int value) + { + state.SetValue("countKey", value); + } + + public static int IncrementMessageCount(this ConversationState state) + { + var count = state.GetValue("countKey"); + state.SetValue("countKey", ++count); + return count; + } + } +} diff --git a/src/samples/Application/AuthenticationBot/appManifest/color.png b/src/samples/Application/AuthenticationBot/appManifest/color.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cf81afbe2f5bafd8563920edfadb78b7b71be6 GIT binary patch literal 3415 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 YJx2difYK9bhlEpZpl7Q49&>" + ], + "webApplicationInfo": { + "id": "${{AAD_APP_CLIENT_ID}}", + "resource": "api://botid-${{AAD_APP_CLIENT_ID}}" + } +} \ No newline at end of file diff --git a/src/samples/Application/AuthenticationBot/appManifest/outline.png b/src/samples/Application/AuthenticationBot/appManifest/outline.png new file mode 100644 index 0000000000000000000000000000000000000000..2c3bf6fa65f152de0cb50056effd5aea7d287ec1 GIT binary patch literal 407 zcmV;I0cie-P)GP9wA4-6No2JPavK^y+J&IdIIqnt|)iz#;q%0#|~})uPXtHpGg|3DT=Cm zRbOQmZzjp~Oa~|w3J0d4$UMjUP`eo9-%ZEed<9c*o{#frSUWpe$h)9<7f||JElr8%Q+a+LHNJ~kNO5B zlRv;1hxJ`;YEbQ%GiTGTR{shYbEe%;Xrq2t9*a`EVNoJ89P+!W;^dkhG3QK~lh@uy z_@!DknGSuYuSg%;OK8pl!P9F+PR@yY6bgl7VhU4=M!!cg{}TWJ002ovPDHLkV1nXO Bp2+|J literal 0 HcmV?d00001 diff --git a/src/samples/Application/AuthenticationBot/appsettings.json b/src/samples/Application/AuthenticationBot/appsettings.json new file mode 100644 index 00000000..a6bcf7de --- /dev/null +++ b/src/samples/Application/AuthenticationBot/appsettings.json @@ -0,0 +1,37 @@ +{ + "ConnectionName": "{{ConnectionName}}", + + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs b/src/samples/Application/messaging.echoBot/EchoBotApplication.cs deleted file mode 100644 index c36e85b6..00000000 --- a/src/samples/Application/messaging.echoBot/EchoBotApplication.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.App; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; -using System.Threading; -using System.Threading.Tasks; - -namespace EchoBot -{ - public class EchoBotApplication : Application - { - public EchoBotApplication(ApplicationOptions options) : base(options) - { - OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); - - // Listen for user to say "/reset" and then delete conversation state - OnMessage("/reset", DeleteStateHandlerAsync); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - OnActivity(ActivityTypes.Message, MessageHandlerAsync); - } - - /// - /// Handles members added events. - /// - public static async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); - } - } - } - - /// - /// Handles "/reset" message. - /// - public static async Task DeleteStateHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); - } - - /// - /// Handles messages except "/reset". - /// - public static async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) - { - // Increment count state. - int count = turnState.Conversation.IncrementMessageCount(); - - await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); - } - } -} diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs index ea7e5a54..5aab8ae7 100644 --- a/src/samples/Application/messaging.echoBot/Program.cs +++ b/src/samples/Application/messaging.echoBot/Program.cs @@ -1,6 +1,7 @@ using EchoBot; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -18,17 +19,47 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Create the bot as a transient. -builder.Services.AddTransient(sp => +// Add bot routes and logic +builder.AddBot(sp => { - return new() + var options = new ApplicationOptions() { - StartTypingTimer = true, + StartTypingTimer = false, TurnStateFactory = () => new TurnState(sp.GetService()) }; -}); -builder.AddBot(); + var app = new Application(options); + + // Display a welcome message + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + }); + + // Listen for user to say "/reset" and then delete conversation state + app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); + }); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => + { + // Increment count state. + int count = turnState.Conversation.IncrementMessageCount(); + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + }); + + return app; +}); var app = builder.Build(); diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs index 3214426a..040d4c23 100644 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -6,6 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App.Authentication; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; @@ -27,7 +29,7 @@ public AuthBot(IConfiguration configuration, PrivateConversationState conversati _logger = logger ?? NullLogger.Instance; _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); _botState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); - _flow = new OAuthFlow("Sign In", "Please sign in", _configuration["ConnectionName"], 30000, null); + _flow = new OAuthFlow(new OAuthSettings() { ConnectionName = _configuration["ConnectionName"] }); } protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) @@ -63,7 +65,7 @@ protected override async Task OnMessageActivityAsync(ITurnContext turnConte } var message = MessageFactory.Text($"You are: {member.Name}."); - var res = await turnContext.SendActivityAsync(message); + var res = await turnContext.SendActivityAsync(message, cancellationToken); } diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs index 9248774b..84661c86 100644 --- a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs +++ b/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs @@ -83,7 +83,7 @@ protected override async Task OnMessageActivityAsync(ITurnContext OnInvokeActivityAsync(ITurnContext pendingActivity.Id = turnContext.Activity.ReplyToId; pendingActivity.Attachments = new List { responseAttachment }; - await turnContext.UpdateActivityAsync(pendingActivity); + await turnContext.UpdateActivityAsync(pendingActivity, cancellationToken); response = JsonDocument.Parse(cardJson); adaptiveCardResponse = new AdaptiveCardInvokeResponse() @@ -145,7 +145,7 @@ protected override async Task OnInvokeActivityAsync(ITurnContext canceledActivity.Type = "message"; canceledActivity.Id = turnContext.Activity.ReplyToId; canceledActivity.Attachments = new List { cancelCardResponse }; - await turnContext.UpdateActivityAsync(canceledActivity); + await turnContext.UpdateActivityAsync(canceledActivity, cancellationToken); response = JsonDocument.Parse(cardJson); adaptiveCardResponse = new AdaptiveCardInvokeResponse() { @@ -164,7 +164,7 @@ protected override async Task OnInvokeActivityAsync(ITurnContext approvedActivity.Id = turnContext.Activity.ReplyToId; approvedActivity.Attachments = new List { approvedAttachment }; - await turnContext.UpdateActivityAsync(approvedActivity); + await turnContext.UpdateActivityAsync(approvedActivity, cancellationToken); response = JsonDocument.Parse(cardJson); adaptiveCardResponse = new AdaptiveCardInvokeResponse() @@ -184,7 +184,7 @@ protected override async Task OnInvokeActivityAsync(ITurnContext rejectedActivity.Id = turnContext.Activity.ReplyToId; rejectedActivity.Attachments = new List { rejectedAttachment }; - await turnContext.UpdateActivityAsync(rejectedActivity); + await turnContext.UpdateActivityAsync(rejectedActivity, cancellationToken); response = JsonDocument.Parse(cardJson); adaptiveCardResponse = new AdaptiveCardInvokeResponse() diff --git a/src/tests/BotBuilder.Testing/TestAdapterTests.cs b/src/tests/BotBuilder.Testing/TestAdapterTests.cs index 91a18ccc..7446e42e 100644 --- a/src/tests/BotBuilder.Testing/TestAdapterTests.cs +++ b/src/tests/BotBuilder.Testing/TestAdapterTests.cs @@ -20,7 +20,7 @@ public async Task TestAdapter_ExceptionTypesOnTest() await Assert.ThrowsAsync(() => new TestFlow(adapter, async (context, cancellationToken) => { - await context.SendActivityAsync(context.Activity.CreateReply("one")); + await context.SendActivityAsync(context.Activity.CreateReply("one"), cancellationToken); }) .Test("foo", (activity) => throw new Exception(uniqueExceptionId)) .StartTestAsync()); @@ -48,7 +48,7 @@ public async Task TestAdapter_ExceptionTypesOnAssertReply() await Assert.ThrowsAsync(() => new TestFlow(adapter, async (context, cancellationToken) => { - await context.SendActivityAsync(context.Activity.CreateReply("one")); + await context.SendActivityAsync(context.Activity.CreateReply("one"), cancellationToken); }) .Send("foo") .AssertReply( @@ -230,15 +230,15 @@ private async Task MyBotLogic(ITurnContext turnContext, CancellationToken cancel switch (turnContext.Activity.Text) { case "count": - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("one")); - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("two")); - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("three")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("one"), cancellationToken); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("two"), cancellationToken); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("three"), cancellationToken); break; case "ignore": break; default: await turnContext.SendActivityAsync( - turnContext.Activity.CreateReply($"echo:{turnContext.Activity.Text}")); + turnContext.Activity.CreateReply($"echo:{turnContext.Activity.Text}"), cancellationToken); break; } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs index 4a0a6b5f..9482bbfb 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/ReplaceDialogTest.cs @@ -152,7 +152,7 @@ public WaterfallWithEndDialog(string id, WaterfallStep[] steps) public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken)) { - await turnContext.SendActivityAsync(MessageFactory.Text("*** WaterfallDialog End ***")); + await turnContext.SendActivityAsync(MessageFactory.Text("*** WaterfallDialog End ***"), cancellationToken); await base.EndDialogAsync(turnContext, instance, reason, cancellationToken); } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs index 8c7f2ae0..747a9f04 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -68,7 +69,7 @@ void CaptureSend(IActivity[] arg) // Act app.AdaptiveCards.OnActionExecute("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -127,8 +128,8 @@ void CaptureSend(IActivity[] arg) // Act app.AdaptiveCards.OnActionExecute("test-verb", handler); - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -166,7 +167,7 @@ public async Task Test_OnActionExecute_RouteSelector_ActivityNotMatched() // Act app.AdaptiveCards.OnActionExecute(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: invoke", exception.Message); @@ -209,7 +210,7 @@ public async Task Test_OnActionSubmit_Verb() // Act app.AdaptiveCards.OnActionSubmit("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.True(called); @@ -251,7 +252,7 @@ public async Task Test_OnActionSubmit_Verb_NotHit() // Act app.AdaptiveCards.OnActionSubmit("not-test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.False(called); @@ -289,7 +290,7 @@ public async Task Test_OnActionSubmit_RouteSelector_ActivityNotMatched() // Act app.AdaptiveCards.OnActionSubmit(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected AdaptiveCards.OnActionSubmit() triggered for activity type: message", exception.Message); @@ -358,7 +359,7 @@ void CaptureSend(IActivity[] arg) // Act app.AdaptiveCards.OnSearch("test-dataset", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -417,7 +418,7 @@ void CaptureSend(IActivity[] arg) // Act app.AdaptiveCards.OnSearch("not-test-dataset", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -461,7 +462,7 @@ public async Task Test_OnSearch_RouteSelector_ActivityNotMatched() // Act app.AdaptiveCards.OnSearch(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected AdaptiveCards.OnSearch() triggered for activity type: invoke", exception.Message); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs index ea7f8339..5f7aac35 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs @@ -7,6 +7,7 @@ using Microsoft.Agents.Core.Models; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -50,8 +51,8 @@ public async Task Test_Application_Route() false); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(messages); @@ -103,7 +104,7 @@ public async Task Test_Application_Routes_Are_Called_InOrder() false); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(selectedRoutes); @@ -154,8 +155,8 @@ public async Task Test_Application_InvokeRoute() true); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(names); @@ -211,7 +212,7 @@ public async Task Test_Application_InvokeRoutes_Are_Called_InOrder() true); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(selectedRoutes); @@ -259,7 +260,7 @@ public async Task Test_Application_InvokeRoutes_Are_Called_First() false); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(selectedRoutes); @@ -315,7 +316,7 @@ public async Task Test_Application_No_InvokeRoute_Matched_Fallback_To_Routes() false); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(selectedRoutes); @@ -361,8 +362,8 @@ public async Task Test_OnActivity_String_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(types); @@ -408,8 +409,8 @@ public async Task Test_OnActivity_Regex_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(types); @@ -456,8 +457,8 @@ public async Task Test_OnActivity_Function_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(types); @@ -519,9 +520,9 @@ public async Task Test_OnActivity_Multiple_Selectors() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Equal(3, types.Count); @@ -580,9 +581,9 @@ public async Task Test_OnConversationUpdate_MembersAdded() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -639,9 +640,9 @@ public async Task Test_OnConversationUpdate_MembersRemoved() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -679,7 +680,7 @@ public async Task Test_OnConversationUpdate_UnknownEventName() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -737,9 +738,9 @@ public async Task Test_OnMessage_String_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(texts); @@ -797,9 +798,9 @@ public async Task Test_OnMessage_Regex_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(texts); @@ -846,8 +847,8 @@ public async Task Test_OnMessage_Function_Selector() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); // Assert Assert.Single(texts); @@ -912,9 +913,9 @@ public async Task Test_OnMessage_Multiple_Selectors() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Equal(3, texts.Count); @@ -973,9 +974,9 @@ public async Task Test_OnMessageReactionsAdded() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -1032,9 +1033,9 @@ public async Task Test_OnMessageReactionsRemoved() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -1102,9 +1103,9 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(ids); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs index 1cd9520e..3c936ba2 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TurnStateConfig.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Nodes; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils @@ -28,7 +29,7 @@ public static async Task GetTurnStateWithConversationStateAsync(Turn string conversationId = activity.Conversation.Id; string userId = activity.From.Id; - await state.LoadStateAsync(turnContext); + await state.LoadStateAsync(turnContext, cancellationToken: CancellationToken.None); return state; } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs index e86926d4..8a1f5bb5 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/ChannelAdapterBracketingTest.cs @@ -64,7 +64,7 @@ public async Task Middlware_ThrowException() async Task EchoWithException(ITurnContext ctx, CancellationToken cancellationToken) { string toEcho = "ECHO:" + ctx.Activity.Text; - await ctx.SendActivityAsync(ctx.Activity.CreateReply(toEcho)); + await ctx.SendActivityAsync(ctx.Activity.CreateReply(toEcho), cancellationToken); throw new Exception(uniqueId); } @@ -81,17 +81,17 @@ public class CatchExceptionMiddleware : IMiddleware { public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) { - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("BEFORE")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("BEFORE"), cancellationToken); try { await next(cancellationToken); } catch (Exception ex) { - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("CAUGHT:" + ex.Message)); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("CAUGHT:" + ex.Message), cancellationToken); } - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("AFTER")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("AFTER"), cancellationToken); } } @@ -99,9 +99,9 @@ public class BeforeAFterMiddlware : IMiddleware { public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) { - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("BEFORE")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("BEFORE"), cancellationToken); await next(cancellationToken); - await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("AFTER")); + await turnContext.SendActivityAsync(turnContext.Activity.CreateReply("AFTER"), cancellationToken); } } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs index 9abc54dc..9c326986 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/Handler/ActivityHandlerTests.cs @@ -1095,11 +1095,11 @@ protected override async Task OnMessageActivityAsync(ITurnContext Task.CompletedTask); turnContext.OnSendActivities((t, a, n) => Task.FromResult(new ResourceResponse[] { new ResourceResponse() })); turnContext.OnUpdateActivity((t, a, n) => Task.FromResult(new ResourceResponse())); - await turnContext.DeleteActivityAsync(activity.GetConversationReference()); - await turnContext.DeleteActivityAsync(activity.Id); - await turnContext.SendActivityAsync(new Activity()); - await turnContext.SendActivitiesAsync(new IActivity[] { new Activity() }); - await turnContext.UpdateActivityAsync(new Activity()); + await turnContext.DeleteActivityAsync(activity.GetConversationReference(), cancellationToken); + await turnContext.DeleteActivityAsync(activity.Id, cancellationToken); + await turnContext.SendActivityAsync(new Activity(), cancellationToken); + await turnContext.SendActivitiesAsync([new Activity()], cancellationToken); + await turnContext.UpdateActivityAsync(new Activity(), cancellationToken); await turnContext.TraceActivityAsync(activity.Name, activity.Value, activity.ValueType, activity.Label); } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs index 37cf2e8c..1d2575d0 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.App.Authentication; +using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; @@ -19,14 +21,20 @@ public class OAuthFlowTests public OAuthFlowTests() { - _flow = new OAuthFlow("Test flow", "testing oauth flow", "connection name", 1000, null); ; + _flow = new OAuthFlow(new OAuthSettings() + { + ConnectionName = "connection name", + Title = "Test flow", + Text = "testing oauth flow", + Timeout = 1000 + }); } [Fact] public async Task SignOutUserAsync_ShouldThrowOnNullUserTokenClient() { var context = new TurnContext(new SimpleAdapter(), new Activity()); - await Assert.ThrowsAsync(async() => await _flow.SignOutUserAsync(context)); + await Assert.ThrowsAsync(async() => await _flow.SignOutUserAsync(context, CancellationToken.None)); } [Fact] @@ -50,11 +58,11 @@ public async Task SignOutUserAsync_ShouldLogOutSuccessfully() context.Services.Set(mockUserTokenClient.Object); //Act - await _flow.SignOutUserAsync(context); + await _flow.SignOutUserAsync(context, CancellationToken.None); // Assert mockUserTokenClient.Verify( - x => x.SignOutUserAsync(It.Is(s => s == userId), It.Is(s => s == _flow.ConnectionName), It.Is(s => s == channelId), It.IsAny()), Times.Once()); + x => x.SignOutUserAsync(It.Is(s => s == userId), It.Is(s => s == _flow.Settings.ConnectionName), It.Is(s => s == channelId), It.IsAny()), Times.Once()); } [Fact] @@ -86,7 +94,7 @@ void ValidateResponses(IActivity[] activities) context.Services.Set(mockUserTokenClient.Object); //Act - var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1)); + var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1), CancellationToken.None); // Assert Assert.Null(result); @@ -116,7 +124,7 @@ void ValidateResponses(IActivity[] activities) var context = new TurnContext(new SimpleAdapter(ValidateResponses), activity); //Act - var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1)); + var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1), CancellationToken.None); // Assert Assert.Null(result); @@ -141,7 +149,7 @@ void ValidateResponses(IActivity[] activities) { Type = ActivityTypes.Invoke, Name = SignInConstants.TokenExchangeOperationName, - Value = new TokenResponse(Channels.Msteams, _flow.ConnectionName, "token", null), + Value = new TokenResponse(Channels.Msteams, _flow.Settings.ConnectionName, "token", null), From = new ChannelAccount { Id = "user-id" }, ChannelId = "channel-id", Text = "invoke", @@ -149,7 +157,7 @@ void ValidateResponses(IActivity[] activities) var context = new TurnContext(new SimpleAdapter(ValidateResponses), activity); //Act - var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1)); + var result = await _flow.ContinueFlowAsync(context, DateTime.UtcNow.AddHours(1), CancellationToken.None); // Assert Assert.Null(result); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/OnTurnErrorTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/OnTurnErrorTests.cs index 2c851f78..47736675 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/OnTurnErrorTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/OnTurnErrorTests.cs @@ -3,6 +3,7 @@ using Microsoft.Agents.BotBuilder.Testing; using System; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -18,7 +19,7 @@ public async Task OnTurnError_Test() { if (exception is NotImplementedException) { - await context.SendActivityAsync(context.Activity.CreateReply(exception.Message)); + await context.SendActivityAsync(context.Activity.CreateReply(exception.Message), CancellationToken.None); } else { diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs index 6ee9ce2c..39d201a8 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs @@ -12,6 +12,7 @@ using Microsoft.Agents.Extensions.Teams.Tests.Model; using Moq; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -56,7 +57,7 @@ public async Task Test_OnConversationUpdate_ChannelCreated() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -100,7 +101,7 @@ public async Task Test_OnConversationUpdate_ChannelRenamed() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -144,7 +145,7 @@ public async Task Test_OnConversationUpdate_ChannelDeleted() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -189,7 +190,7 @@ public async Task Test_OnConversationUpdate_ChannelRestored() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -232,7 +233,7 @@ public async Task Test_OnConversationUpdate_TeamRenamed() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -275,7 +276,7 @@ public async Task Test_OnConversationUpdate_TeamDeleted() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -318,7 +319,7 @@ public async Task Test_OnConversationUpdate_TeamHardDeleted() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -361,7 +362,7 @@ public async Task Test_OnConversationUpdate_TeamArchived() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -404,7 +405,7 @@ public async Task Test_OnConversationUpdate_TeamUnarchived() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -447,7 +448,7 @@ public async Task Test_OnConversationUpdate_TeamRestored() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -512,9 +513,9 @@ public async Task Test_OnConversationUpdate_SingleEvent() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -581,9 +582,9 @@ public async Task Test_OnConversationUpdate_MultipleEvents() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Equal(2, names.Count); @@ -652,9 +653,9 @@ public async Task Test_OnConversationUpdate_BypassNonTeamsEvent() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -715,9 +716,9 @@ public async Task Test_OnMessageEdit() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -778,9 +779,9 @@ public async Task Test_OnMessageUnDelete() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -843,9 +844,9 @@ public async Task Test_OnMessageDelete() }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(names); @@ -922,10 +923,10 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); - await app.OnTurnAsync(turnContext4); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); + await app.OnTurnAsync(turnContext4, CancellationToken.None); // Assert Assert.Single(names); @@ -1014,10 +1015,10 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); - await app.OnTurnAsync(turnContext4); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); + await app.OnTurnAsync(turnContext4, CancellationToken.None); // Assert Assert.Single(names); @@ -1096,9 +1097,9 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(ids); @@ -1177,9 +1178,9 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(ids); @@ -1251,9 +1252,9 @@ void CaptureSend(IActivity[] arg) }); // Act - await app.OnTurnAsync(turnContext1); - await app.OnTurnAsync(turnContext2); - await app.OnTurnAsync(turnContext3); + await app.OnTurnAsync(turnContext1, CancellationToken.None); + await app.OnTurnAsync(turnContext2, CancellationToken.None); + await app.OnTurnAsync(turnContext3, CancellationToken.None); // Assert Assert.Single(ids); @@ -1298,7 +1299,7 @@ public async Task Test_OnTeamsReadReceipt() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Single(names); @@ -1339,7 +1340,7 @@ public async Task Test_OnTeamsReadReceipt_IncorrectName() }); // Act - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Empty(names); diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs index 035e8124..43dcf80b 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MeetingsTests.cs @@ -7,6 +7,7 @@ using Microsoft.Agents.Extensions.Teams.App; using Microsoft.Agents.Extensions.Teams.Tests.Model; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -38,7 +39,7 @@ public async Task Test_OnStart() // Act foreach (var turnContext in turnContexts) { - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); } // Assert @@ -70,7 +71,7 @@ public async Task Test_OnEnd() // Act foreach (var turnContext in turnContexts) { - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); } // Assert @@ -102,7 +103,7 @@ public async Task Test_OnParticipantsJoin() // Act foreach (var turnContext in turnContexts) { - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); } // Assert @@ -134,7 +135,7 @@ public async Task Test_OnParticipantsLeave() // Act foreach (var turnContext in turnContexts) { - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); } // Assert diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs index 1c3f0cda..aedb7215 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/MessageExtensionsTests.cs @@ -13,6 +13,7 @@ using Moq; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -70,7 +71,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnSubmitAction("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -124,7 +125,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnSubmitAction("not-test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -161,7 +162,7 @@ public async Task Test_OnSubmitAction_RouteSelector_ActivityNotMatched() // Act app.MessageExtensions.OnSubmitAction(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected MessageExtensions.OnSubmitAction() triggered for activity type: invoke", exception.Message); @@ -222,7 +223,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnBotMessagePreviewEdit("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -280,7 +281,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnBotMessagePreviewEdit("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -317,7 +318,7 @@ public async Task Test_OnBotMessagePreviewEdit_RouteSelector_ActivityNotMatched( // Act app.MessageExtensions.OnBotMessagePreviewEdit(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected MessageExtensions.OnBotMessagePreviewEdit() triggered for activity type: invoke", exception.Message); @@ -376,7 +377,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnBotMessagePreviewSend("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -433,7 +434,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnBotMessagePreviewSend("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -469,7 +470,7 @@ public async Task Test_OnBotMessagePreviewSend_RouteSelector_ActivityNotMatched( // Act app.MessageExtensions.OnBotMessagePreviewSend(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected MessageExtensions.OnBotMessagePreviewSend() triggered for activity type: invoke", exception.Message); @@ -517,7 +518,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnFetchTask("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -563,7 +564,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnFetchTask("not-test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -600,7 +601,7 @@ public async Task Test_OnFetchTask_RouteSelector_ActivityNotMatched() // Act app.MessageExtensions.OnFetchTask(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected MessageExtensions.OnFetchTask() triggered for activity type: invoke", exception.Message); @@ -664,7 +665,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnQuery("test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -723,7 +724,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnQuery("not-test-command", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -760,7 +761,7 @@ public async Task Test_OnQuery_RouteSelector_NotMatched() // Act app.MessageExtensions.OnQuery(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected MessageExtensions.OnQuery() triggered for activity type: invoke", exception.Message); @@ -808,7 +809,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnSelectItem(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -859,7 +860,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnSelectItem(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -911,7 +912,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnQueryLink(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -953,7 +954,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnQueryLink(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -1005,7 +1006,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnAnonymousQueryLink(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -1047,7 +1048,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnAnonymousQueryLink(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -1093,7 +1094,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnQueryUrlSetting(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -1134,7 +1135,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnAnonymousQueryLink(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -1182,7 +1183,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnConfigureSettings(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -1222,7 +1223,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnConfigureSettings(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -1263,7 +1264,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnCardButtonClicked(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -1303,7 +1304,7 @@ void CaptureSend(IActivity[] arg) // Act app.MessageExtensions.OnCardButtonClicked(handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs index c35944bb..9b47b8f2 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/TaskModulesTests.cs @@ -13,6 +13,7 @@ using Moq; using System; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -69,7 +70,7 @@ void CaptureSend(IActivity[] arg) // Act app.TaskModules.OnFetch("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -122,7 +123,7 @@ void CaptureSend(IActivity[] arg) // Act app.TaskModules.OnFetch("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -159,7 +160,7 @@ public async Task Test_OnFetch_RouteSelector_ActivityNotMatched() // Act app.TaskModules.OnFetch(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected TaskModules.OnFetch() triggered for activity type: invoke", exception.Message); @@ -214,7 +215,7 @@ void CaptureSend(IActivity[] arg) // Act app.TaskModules.OnSubmit("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.NotNull(activitiesToSend); @@ -268,7 +269,7 @@ void CaptureSend(IActivity[] arg) // Act app.TaskModules.OnSubmit("test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext, CancellationToken.None); // Assert Assert.Null(activitiesToSend); @@ -305,7 +306,7 @@ public async Task Test_OnSubmit_RouteSelector_ActivityNotMatched() // Act app.TaskModules.OnSubmit(routeSelector, handler); - var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext)); + var exception = await Assert.ThrowsAsync(async () => await app.OnTurnAsync(turnContext, CancellationToken.None)); // Assert Assert.Equal("Unexpected TaskModules.OnSubmit() triggered for activity type: invoke", exception.Message); diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs index f959252b..fab07d6b 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/BackgroundActivityService/HostedActivityServiceTests.cs @@ -25,19 +25,20 @@ public void Constructor_ShouldThrowWithNullConfig() var adapter = new TestAdapter(); var queue = new ActivityTaskQueue(); var logger = new Mock>(); + var sp = new Mock(); - Assert.Throws(() => new HostedActivityService(null, bot, adapter, queue, logger.Object)); + Assert.Throws(() => new HostedActivityService(sp.Object, null, adapter, queue, logger.Object)); } [Fact] - public void Constructor_ShouldThrowWithNullBot() + public void Constructor_ShouldThrowWithNullServiceProvider() { var config = new ConfigurationBuilder().Build(); var adapter = new TestAdapter(); var queue = new ActivityTaskQueue(); var logger = new Mock>(); - Assert.Throws(() => new HostedActivityService(config, null, adapter, queue, logger.Object)); + Assert.Throws(() => new HostedActivityService(null, config, adapter, queue, logger.Object)); } [Fact] @@ -47,8 +48,9 @@ public void Constructor_ShouldThrowWithNullAdapter() var bot = new ActivityHandler(); var queue = new ActivityTaskQueue(); var logger = new Mock>(); + var sp = new Mock(); - Assert.Throws(() => new HostedActivityService(config, bot, null, queue, logger.Object)); + Assert.Throws(() => new HostedActivityService(sp.Object, config, null, queue, logger.Object)); } [Fact] @@ -58,8 +60,9 @@ public void Constructor_ShouldThrowWithNullActivityTaskQueue() var bot = new ActivityHandler(); var adapter = new TestAdapter(); var logger = new Mock>(); + var sp = new Mock(); - Assert.Throws(() => new HostedActivityService(config, bot, adapter, null, logger.Object)); + Assert.Throws(() => new HostedActivityService(sp.Object, config, adapter, null, logger.Object)); } [Fact] @@ -69,10 +72,11 @@ public async Task Constructor_ShouldInstantiateNullLogger() var bot = new ActivityHandler(); var adapter = new TestAdapter(); var queue = new ActivityTaskQueue(); + var sp = new Mock(); try { - var service = new HostedActivityService(config, bot, adapter, queue, null); + var service = new HostedActivityService(sp.Object, config, adapter, queue, null); await service.StopAsync(CancellationToken.None); } catch (Exception) @@ -154,8 +158,9 @@ private static Record UseRecord() var bot = new Mock(); var adapter = new Mock(); var logger = new Mock>(); + var sp = new Mock(); - var service = new HostedActivityService(config, bot.Object, adapter.Object, queue, logger.Object); + var service = new HostedActivityService(sp.Object, config, adapter.Object, queue, logger.Object); return new(service, queue, bot, adapter, logger); } diff --git a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs index 19ad7929..c443e695 100644 --- a/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs +++ b/src/tests/Microsoft.Agents.Hosting.AspNetCore/CloudAdapterTests.cs @@ -125,7 +125,7 @@ public async Task ProcessAsync_ShouldSetUnauthorized() var context = CreateHttpContext(new(ActivityTypes.Message, conversation: new(id: "test"))); var bot = new ActivityHandler(); - record.Queue.Setup(e => e.QueueBackgroundActivity(It.IsAny(), It.IsAny())) + record.Queue.Setup(e => e.QueueBackgroundActivity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new UnauthorizedAccessException()) .Verifiable(Times.Once); @@ -154,7 +154,7 @@ public async Task ProcessAsync_ShouldQueueActivity() var context = CreateHttpContext(new(ActivityTypes.Message, conversation: new(id: "test"))); var bot = new ActivityHandler(); - record.Queue.Setup(e => e.QueueBackgroundActivity(It.IsAny(), It.IsAny())) + record.Queue.Setup(e => e.QueueBackgroundActivity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Verifiable(Times.Once); await record.Adapter.ProcessAsync(context.Request, context.Response, bot, CancellationToken.None); @@ -221,7 +221,7 @@ private static Record UseRecord() var logger = new Mock>(); var middleware = new Mock(); - var adapter = new TestAdapter(factory.Object, queue.Object, logger.Object, new AdapterOptions() { Async = true }, middleware.Object); + var adapter = new TestAdapter(factory.Object, queue.Object, logger.Object, middlewares: middleware.Object); return new(adapter, factory, queue, logger); } @@ -241,9 +241,8 @@ private class TestAdapter( IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, ILogger logger = null, - AdapterOptions options = null, params BotBuilder.IMiddleware[] middlewares) - : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, options, middlewares) + : CloudAdapter(channelServiceClientFactory, activityTaskQueue, logger, null, middlewares) { public override Task ProcessActivityAsync(ClaimsIdentity claimsIdentity, IActivity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { diff --git a/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs index b4292cde..a4698755 100644 --- a/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/AutoSaveStateMiddlewareTests.cs @@ -37,11 +37,11 @@ public async Task AutoSaveStateMiddleware_DualReadWrite() { if (context.Activity.Text == "get userCount") { - await context.SendActivityAsync(context.Activity.CreateReply($"{userCount}")); + await context.SendActivityAsync(context.Activity.CreateReply($"{userCount}"), cancellationToken); } else if (context.Activity.Text == "get convCount") { - await context.SendActivityAsync(context.Activity.CreateReply($"{convCount}")); + await context.SendActivityAsync(context.Activity.CreateReply($"{convCount}"), cancellationToken); } } @@ -112,11 +112,11 @@ public async Task AutoSaveStateMiddleware_Chain() { if (context.Activity.Text == "get userCount") { - await context.SendActivityAsync(context.Activity.CreateReply($"{userCount}")); + await context.SendActivityAsync(context.Activity.CreateReply($"{userCount}"), cancellationToken); } else if (context.Activity.Text == "get convCount") { - await context.SendActivityAsync(context.Activity.CreateReply($"{convCount}")); + await context.SendActivityAsync(context.Activity.CreateReply($"{convCount}"), cancellationToken); } } diff --git a/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs index 40d6f475..cc0ab748 100644 --- a/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs +++ b/src/tests/Microsoft.Agents.State.Tests/TurnStateTests.cs @@ -5,6 +5,7 @@ using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -30,7 +31,7 @@ public async Task TurnState_LoadAsync() var turnContext = TestUtilities.CreateEmptyContext(); { var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); - await turnState.LoadStateAsync(turnContext, false); + await turnState.LoadStateAsync(turnContext, cancellationToken: CancellationToken.None, force:false); var userCount = turnState.GetValue("user.userCount", () => 0); Assert.Equal(0, userCount); @@ -43,13 +44,13 @@ public async Task TurnState_LoadAsync() Assert.Equal(10, turnState.GetValue("user.userCount", () => 0)); Assert.Equal(20, turnState.GetValue("conversation.convCount", () => 0)); - await turnState.SaveStateAsync(turnContext, false); + await turnState.SaveStateAsync(turnContext, cancellationToken: CancellationToken.None, force:false); } { var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); - await turnState.LoadStateAsync(turnContext); + await turnState.LoadStateAsync(turnContext, cancellationToken: CancellationToken.None); var userCount = turnState.GetValue("user.userCount", () => 0); Assert.Equal(10, userCount); @@ -181,7 +182,7 @@ public async Task TurnState_ReturnsDefaultForNullValueType() var storage = new MemoryStorage(); var turnContext = TestUtilities.CreateEmptyContext(); var turnState = new TurnState(new UserState(storage), new ConversationState(storage)); - await turnState.LoadStateAsync(turnContext, false); + await turnState.LoadStateAsync(turnContext, cancellationToken: CancellationToken.None, force:false); var userObject = turnState.GetValue("user.userStateObject"); Assert.Null(userObject); @@ -212,7 +213,7 @@ public async Task TurnState_SaveAsync() var turnState = new TurnState(userState, convState); var context = TestUtilities.CreateEmptyContext(); - await turnState.LoadStateAsync(context); + await turnState.LoadStateAsync(context, cancellationToken: CancellationToken.None); var userCount = userState.GetValue("userCount", () => 0); Assert.Equal(0, userCount); @@ -222,7 +223,7 @@ public async Task TurnState_SaveAsync() userState.SetValue("userCount", 10); convState.SetValue("convCount", 20); - await turnState.SaveStateAsync(context); + await turnState.SaveStateAsync(context, cancellationToken: CancellationToken.None); userCount = userState.GetValue("userCount", () => 0); Assert.Equal(10, userCount); diff --git a/src/tests/Microsoft.Agents.Storage.Tests/BlobsStorageTests.cs b/src/tests/Microsoft.Agents.Storage.Tests/BlobsStorageTests.cs index feb79f80..08610e89 100644 --- a/src/tests/Microsoft.Agents.Storage.Tests/BlobsStorageTests.cs +++ b/src/tests/Microsoft.Agents.Storage.Tests/BlobsStorageTests.cs @@ -155,7 +155,7 @@ public async Task WriteAsyncHttpPreconditionFailure() var changes = new Dictionary { { "key", new StoreItem() } }; - await Assert.ThrowsAsync(() => _storage.WriteAsync(changes)); + await Assert.ThrowsAsync(() => _storage.WriteAsync(changes)); } [Fact] From 3cd0143ca3f45a28cfefeb2f5658fc15da153fae Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 25 Feb 2025 14:16:42 -0600 Subject: [PATCH 47/60] Handling manual signin with success/failure callbacks. --- .../Prompts/OAuthPrompt.cs | 2 +- .../Prompts/OAuthPromptSettings.cs | 3 +- .../TokenService => }/ActivityUtilities.cs | 2 +- .../App/AdaptiveCards/AdaptiveCardsFeature.cs | 1 - .../App/Application.cs | 172 +------ .../App/ApplicationOptions.cs | 7 +- .../App/Authentication/AuthUtilities.cs | 101 ----- .../Authentication/AuthenticationManager.cs | 143 ------ .../App/TypingTimer.cs | 6 +- .../App/UserAuth/UserAuthenticationFeature.cs | 428 ++++++++++++++++++ .../UserAuthenticationOptions.cs} | 57 ++- .../AuthException.cs | 2 +- .../IUserAuthentication.cs} | 6 +- .../UserAuth/IUserAuthenticationDispatcher.cs | 50 ++ .../SignInResponse.cs | 4 +- .../SignInStatus.cs | 2 +- .../TokenService/DedupeTokenExchange.cs | 6 +- .../TokenService/OAuthAuthentication.cs | 8 +- .../TokenService/OAuthBotAuthentication.cs | 2 +- .../TokenService/OAuthFlow.cs | 2 +- .../TokenService/OAuthSettings.cs | 6 +- .../TokenService/UserTokenClientWrapper.cs | 2 +- .../UserAuth/UserAuthenticationDispatcher.cs | 127 ++++++ .../MessageExtensionsFeature.cs | 1 - .../App/TaskModules/TaskModulesFeature.cs | 1 - .../App/TeamsApplication.cs | 1 - .../Application/AuthenticationBot/Program.cs | 54 ++- src/samples/AuthenticationBot/AuthBot.cs | 3 +- .../App/TestUtils/TestApplication.cs | 19 + .../TestUserAuthenticationFeature.cs | 13 + .../App/UserAuthenticationFeatureTests.cs | 194 ++++++++ .../OAuthFlowTests.cs | 3 +- 32 files changed, 963 insertions(+), 465 deletions(-) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{Authentication/TokenService => }/ActivityUtilities.cs (88%) delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs delete mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{Authentication/AuthenticationOptions.cs => UserAuth/UserAuthenticationOptions.cs} (54%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/AuthException.cs (96%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication/IAuthentication.cs => UserAuth/IUserAuthentication.cs} (94%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthenticationDispatcher.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/SignInResponse.cs (88%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/SignInStatus.cs (90%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/DedupeTokenExchange.cs (97%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/OAuthAuthentication.cs (94%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/OAuthBotAuthentication.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/OAuthFlow.cs (99%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/OAuthSettings.cs (92%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/{App/Authentication => UserAuth}/TokenService/UserTokenClientWrapper.cs (97%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/UserAuthenticationDispatcher.cs create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 4e256808..94ccd849 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Authentication; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs index 3ae3a5e0..4d006c16 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPromptSettings.cs @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; + +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; namespace Microsoft.Agents.BotBuilder.Dialogs { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs similarity index 88% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs index cdeee211..01a8c406 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/ActivityUtilities.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityUtilities.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System.Net; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.App { public static class ActivityUtilities { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index d147429c..24969d4e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 71e65d9f..864dd707 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -2,18 +2,15 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder.App.AdaptiveCards; -using Microsoft.Agents.BotBuilder.App.Authentication; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.App.UserAuth; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using static Microsoft.Agents.BotBuilder.App.Authentication.AuthenticationManager; namespace Microsoft.Agents.BotBuilder.App { @@ -22,7 +19,7 @@ namespace Microsoft.Agents.BotBuilder.App /// public class Application : IBot { - private readonly AuthenticationManager _authentication; + private readonly UserAuthenticationFeature _authentication; private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; @@ -33,8 +30,6 @@ public class Application : IBot private readonly ConcurrentQueue _beforeTurn; private readonly ConcurrentQueue _afterTurn; - private readonly SelectorAsync? _startSignIn; - /// /// Creates a new Application instance. /// @@ -52,26 +47,18 @@ public Application(ApplicationOptions options) Options.TurnStateFactory = () => new TurnState(); } - AdaptiveCards = new AdaptiveCardsFeature(this); - _routes = new ConcurrentQueue(); _invokeRoutes = new ConcurrentQueue(); _beforeTurn = new ConcurrentQueue(); _afterTurn = new ConcurrentQueue(); - if (options.Authentication != null) - { - _authentication = new AuthenticationManager(this, options.Authentication); + // Application Features - if (options.Authentication.AutoSignIn != null) - { - _startSignIn = options.Authentication.AutoSignIn; - } - else - { - // If AutoSignIn wasn't specified, default to true. - _startSignIn = (context, cancellationToken) => Task.FromResult(true); - } + AdaptiveCards = new AdaptiveCardsFeature(this); + + if (options.UserAuthentication != null) + { + _authentication = new UserAuthenticationFeature(this, options.UserAuthentication); } } @@ -83,13 +70,13 @@ public Application(ApplicationOptions options) /// /// Accessing authentication specific features. /// - public AuthenticationManager Authentication + public UserAuthenticationFeature Authentication { get { if (_authentication == null) { - throw new ArgumentException("The Application.Authentication property is unavailable because no authentication options were configured."); + throw new InvalidOperationException("The Application.UserAuthentication property is unavailable because no authentication options were configured."); } return _authentication; @@ -525,71 +512,8 @@ public void StopTypingTimer() #endregion - #region User Authentication - - /// - /// If the user is signed in, get the access token. If not, triggers the sign in flow for the provided authentication setting name - /// and returns.In this case, the bot should end the turn until the sign in flow is completed. - /// - /// - /// Use this method to get the access token for a user that is signed in to the bot. - /// If the user isn't signed in, this method starts the sign-in flow. - /// The bot should end the turn in this case until the sign-in flow completes and the user is signed in. - /// - /// The turn context. - /// - /// The name of the authentication setting. - /// - /// The cancellation token. - /// - public Task GetTokenOrStartSignInAsync(ITurnContext turnContext, ITurnState turnState, string settingName, SignInCompletionHandlerAsync completionHandler, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - - /* - // TODO: AddRoute - - // User is currently not in sign in flow - if (AuthUtilities.UserInSignInFlow(turnState) == null) - { - AuthUtilities.SetUserInSignInFlow(turnState, settingName); - } - else - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - throw new InvalidOperationException("Invalid sign in flow state. Cannot start sign in when already started"); - } - - SignInResponse response = await Authentication.SignUserInAsync(turnContext, settingName, cancellationToken).ConfigureAwait(false); - - if (response.Status == SignInStatus.Error) - { - string message = response.Error!.ToString(); - - // TODO: the current activity shouldn't trigger an error in the case of manual signin. Problem is, the flow impls - // currently ignore all but specific activities. - if (response.Cause == AuthExceptionReason.InvalidActivity) - { - message = $"User is not signed in and cannot start sign in flow for this activity: {response.Error}"; - } - - throw new InvalidOperationException($"Error occurred while trying to authenticate user: {message}, flow: {settingName}"); - } - - // Call the handler immediately if the user was already signed in. - if (response.Status == SignInStatus.Complete) - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - AuthUtilities.SetTokenInState(turnState, settingName, response.TokenResponse.Token); - await completionHandler(turnContext, turnState, settingName, response, cancellationToken).ConfigureAwait(false); - } - - */ - } - - #endregion - #region Turn Handling + /// /// Called by the adapter (for example, a ) /// at runtime in order to process an inbound . @@ -622,7 +546,7 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // Remove @mentions if (Options.RemoveRecipientMention && ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { - // TODO: What about normalizing mentions? + // TODO: What about normalizing mentions (via integrated NormalizeMentions)? turnContext.Activity.Text = turnContext.Activity.RemoveRecipientMention(); } @@ -634,76 +558,10 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc // Handle user auth if (_authentication != null) { - // If a flow is active, continue that. - string? flowName = AuthUtilities.UserInSignInFlow(turnState); - bool shouldStartSignIn = _startSignIn != null && await _startSignIn(turnContext, cancellationToken); - - if (shouldStartSignIn || flowName != null) + // Start sign in for default flow + if (await _authentication.SignUserInAsync(turnContext, turnState, cancellationToken: cancellationToken).ConfigureAwait(false)) { - if (flowName == null) - { - // Auth flow hasn't start yet. - flowName = _authentication.Default; - - // Bank the Activity so it can be executed after sign in is complete. - AuthUtilities.SetUserSigninActivity(turnContext, turnState); - } - - SignInResponse response = await _authentication.SignUserInAsync(turnContext, flowName, cancellationToken: cancellationToken).ConfigureAwait(false); - - if (response.Status == SignInStatus.Pending) - { - // Requires user action, save state and stop processing current activity. Done with this turn. - AuthUtilities.SetUserInSignInFlow(turnState, flowName); - await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); - return; - } - - if (response.Status == SignInStatus.Error && response.Cause != AuthExceptionReason.InvalidActivity) - { - await _authentication.Get(flowName).ResetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); - AuthUtilities.DeleteUserInSignInFlow(turnState); - AuthUtilities.DeleteUserSigninActivity(turnState); - await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); - - if (Options.Authentication.SignInFailedMessage == null) - { - throw response.Error; - } - - await turnContext.SendActivitiesAsync(Options.Authentication.SignInFailedMessage(flowName, response), cancellationToken).ConfigureAwait(false); - return; - } - - if (response.Status == SignInStatus.Complete) - { - AuthUtilities.DeleteUserInSignInFlow(turnState); - AuthUtilities.SetTokenInState(turnState, flowName, response.TokenResponse.Token); - // TODO: should probably call "_authentication.Get(flowName).ResetState?", but is this safe? - - var signInActivity = AuthUtilities.DeleteUserSigninActivity(turnState); - if (signInActivity != null) - { - // If the current activity matches the one used to trigger sign in, then - // this is because the user received a token that didn't involve a multi-turn - // flow. No further action needed. - if (!ProtocolJsonSerializer.Equals(signInActivity, turnContext.Activity)) - { - // Since we could be handling an Invoke in this turn, and ITurnContext.Activity is readonly, - // we need to continue the conversation in a different turn with the original Activity that triggered sign in. - await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); - await Options.Adapter.ProcessProactiveAsync(turnContext.Identity, signInActivity, this, cancellationToken).ConfigureAwait(false); - return; - } - } - - if (Options.Authentication.CompletedMessage != null) - { - await turnContext.SendActivitiesAsync(Options.Authentication.CompletedMessage(flowName, response), cancellationToken).ConfigureAwait(false); - } - } - - // If we got this far, fall through to normal Activity route handling. + return; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index 46696930..118ab9a6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -5,9 +5,8 @@ using System; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using System.Collections.Generic; -using Microsoft.Agents.BotBuilder.App.Authentication; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Storage; +using Microsoft.Agents.BotBuilder.App.UserAuth; namespace Microsoft.Agents.BotBuilder.App { @@ -54,8 +53,8 @@ public class ApplicationOptions public bool StartTypingTimer { get; set; } = true; /// - /// Optional. Options used to enable authentication for the application. + /// Optional. Options used to enable user authentication for the application. /// - public AuthenticationOptions Authentication { get; set; } + public UserAuthenticationOptions UserAuthentication { get; set; } } } \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs deleted file mode 100644 index ac08b1ef..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthUtilities.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; - -namespace Microsoft.Agents.BotBuilder.App.Authentication -{ - /// - /// Authentication utilities - /// - internal class AuthUtilities - { - private const string IS_SIGNED_IN_KEY = "__InSignInFlow__"; - private const string SIGNIN_ACTIVITY_KEY = "__SignInFlowActivity__"; - - public static string GetTokenInState(ITurnState turnState, string name) - { - if (turnState.Temp.AuthTokens.TryGetValue(name, out var token)) - { - return token; - } - return null; - } - - /// - /// Set token in state - /// - /// The turn state - /// The name of token - /// The value of token - public static void SetTokenInState(ITurnState state, string name, string token) - { - state.Temp.AuthTokens[name] = token; - } - - /// - /// Delete token from turn state - /// - /// The turn state - /// The name of token - public static void DeleteTokenFromState(ITurnState turnState, string name) - { - if (turnState.Temp.AuthTokens != null && turnState.Temp.AuthTokens.ContainsKey(name)) - { - turnState.Temp.AuthTokens.Remove(name); - } - } - - /// - /// Determines if the user is in the sign in flow. - /// - /// The turn state. - /// The connection setting name if the user is in sign in flow. Otherwise null. - public static string? UserInSignInFlow(ITurnState turnState) - { - string? value = turnState.User.GetValue(IS_SIGNED_IN_KEY); - - if (value == string.Empty || value == null) - { - return null; - } - - return value; - } - - /// - /// Update the turn state to indicate the user is in the sign in flow by providing the authentication setting name used. - /// - /// The turn state. - /// The connection setting name defined when configuring the authentication options within the application class. - public static void SetUserInSignInFlow(ITurnState turnState, string settingName) - { - turnState.User.SetValue(IS_SIGNED_IN_KEY, settingName); - } - - /// - /// Delete the user in sign in flow state from the turn state. - /// - /// The turn state. - public static void DeleteUserInSignInFlow(ITurnState turnState) - { - turnState.User.DeleteValue(IS_SIGNED_IN_KEY); - } - - public static void SetUserSigninActivity(ITurnContext turnContext, ITurnState turnState) - { - turnState.User.SetValue(SIGNIN_ACTIVITY_KEY, turnContext.Activity); - } - - public static IActivity DeleteUserSigninActivity(ITurnState turnState) - { - var activity = turnState.User.GetValue(SIGNIN_ACTIVITY_KEY); - if (activity != null) - { - turnState.User.DeleteValue(SIGNIN_ACTIVITY_KEY); - } - return activity; - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs deleted file mode 100644 index 84c49c80..00000000 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationManager.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Agents.BotBuilder.App.Authentication -{ - /// - /// The user authentication manager - /// - public class AuthenticationManager - { - public delegate Task SignInCompletionHandlerAsync(ITurnContext turnContext, ITurnState turnState, string settingName, SignInResponse completionResponse, CancellationToken cancellationToken); - - private Dictionary _authentications; - - /// - /// The default authentication setting name. - /// - public string Default { get; } - - /// - /// Creates a new instance of the class - /// - /// The application. - /// The authentication options - /// The storage to use. - /// Throws when the options does not contain authentication handlers - public AuthenticationManager(Application app, AuthenticationOptions options) - { - if (options._authenticationSettings.Count == 0) - { - throw new ArgumentException("Authentications setting is empty"); - } - - _authentications = []; - - // If developer does not specify default authentication, set default to the first one in the options - Default = options.Default ?? options._authenticationSettings.First().Key; - - foreach (string key in options._authenticationSettings.Keys) - { - object setting = options._authenticationSettings[key]; - if (setting is OAuthSettings oauthSetting) - { - _authentications.Add(key, new OAuthAuthentication(key, oauthSetting, options.Storage)); - } - //else if (setting is TeamsSsoSettings teamsSsoSettings) - //{ - // _authentications.Add(key, new TeamsSsoAuthentication(app, key, teamsSsoSettings, storage)); - //} - } - } - - /// - /// Sign in a user. - /// - /// - /// On success, this will put the token in ITurnState.Temp.AuthTokens[settingName]. - /// - /// The turn context - /// The name of the authentication handler to use. If null, the default handler name is used. - /// The cancellation token - /// The sign in status - public async Task SignUserInAsync(ITurnContext context, string settingName, CancellationToken cancellationToken = default) - { - settingName = settingName ?? Default; - - IAuthentication auth = Get(settingName); - TokenResponse token; - try - { - token = await auth.SignInUserAsync(context, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - SignInResponse newResponse = new(SignInStatus.Error) - { - Error = ex, - Cause = AuthExceptionReason.Other - }; - if (ex is AuthException authEx) - { - newResponse.Cause = authEx.Cause; - } - - return newResponse; - } - - if (token != null) - { - return new SignInResponse(SignInStatus.Complete) - { - TokenResponse = token - }; - } - - return new SignInResponse(SignInStatus.Pending); - } - - /// - /// Signs out a user. - /// - /// The turn context - /// The turn state - /// Optional. The name of the authentication handler to use. If not specified, the default handler name is used. - /// The cancellation token - public async Task SignOutUserAsync(ITurnContext context, ITurnState state, string? settingName = null, CancellationToken cancellationToken = default) - { - if (settingName == null) - { - settingName = Default; - } - - IAuthentication auth = Get(settingName); - await auth.SignOutUserAsync(context, cancellationToken).ConfigureAwait(false); - AuthUtilities.DeleteTokenFromState(state, settingName); - } - - /// - /// Get an authentication class via name - /// - /// The name of authentication class - /// The authentication class - /// When cannot find the class with given name - public IAuthentication Get(string name) - { - if (_authentications.ContainsKey(name)) - { - return _authentications[name]; - } - - throw new InvalidOperationException($"Could not find authentication handler with name '{name}'."); - } - } -} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs index fe01cfc7..ad9e7707 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/TypingTimer.cs @@ -38,7 +38,7 @@ internal class TypingTimer : IDisposable /// /// The interval in milliseconds to send "typing" activity. /// Initial delay - public TypingTimer(int interval = 5000, int initialDelay = 500) + public TypingTimer(int interval = 2000, int initialDelay = 500) { _interval = interval; _initialDelay = initialDelay; @@ -123,7 +123,7 @@ private async void SendTypingActivity(object state) if (IsRunning()) { _timer?.Change(_interval, Timeout.Infinite); - _send.WaitOne(); + _send.Set(); } } } @@ -145,7 +145,7 @@ private Task StopTimerWhenSendMessageActivityHandlerAsync(IT if (activity.Type == ActivityTypes.Message) { // This will block ITurnContext.SendActivity until the typing timer is done. - _send.Set(); + _send.WaitOne(); // Stop timer Dispose(); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs new file mode 100644 index 00000000..7bc89523 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs @@ -0,0 +1,428 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.UserAuth; +using System.Threading.Tasks; +using System.Threading; +using System; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; + +namespace Microsoft.Agents.BotBuilder.App.UserAuth +{ + /// + /// User Authentication supports and extensible number of OAuth flows. + /// + /// Auto Sign In: + /// If enabled in UserAuthenticationOptions, sign in starts automatically after the first Message the user sends. When + /// the sign in is complete, the turn continues with the original message. On failure, an optional message is sent, otherwise + /// and exception thrown. + /// + /// Manual Sign In: + /// is used to get a cached token or start the sign in. In either case, the + /// and + /// should + /// be set to handle continuation. That is, after calling GetTokenOrStartSignInAsync, the turn should be considered complete, + /// and performing actions after that could be confusing. + /// + public class UserAuthenticationFeature + { + private readonly SelectorAsync? _startSignIn; + private const string IS_SIGNED_IN_KEY = "__InSignInFlow__"; + private const string SIGNIN_ACTIVITY_KEY = "__SignInFlowActivity__"; + private const string SignInCompletionEventName = "application/vnd.microsoft.SignInCompletion"; + private readonly IUserAuthenticationDispatcher _dispatcher; + private readonly UserAuthenticationOptions _options; + private readonly Application _app; + + /// + /// Callback when user sign in success + /// + private Func? _userSignInSuccessHandler; + + /// + /// Callback when user sign in fail + /// + private Func? _userSignInFailureHandler; + + public string Default { get; private set; } + + public UserAuthenticationFeature(Application app, UserAuthenticationOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _dispatcher = new UserAuthenticationDispatcher([.. _options.Handlers]); + _app = app ?? throw new ArgumentNullException(nameof(app)); + + if (_options.AutoSignIn != null) + { + _startSignIn = _options.AutoSignIn; + } + else + { + // If AutoSignIn wasn't specified, default to true. + _startSignIn = (context, cancellationToken) => Task.FromResult(true); + } + + Default = _options.Default ?? _dispatcher.Default.Name; + AddManualSignInCompletionHandler(); + } + + /// + /// This starts the sign in flow. + /// + /// + /// + /// + /// + /// + internal async Task SignUserInAsync(ITurnContext turnContext, ITurnState turnState, string flowName = null, CancellationToken cancellationToken = default) + { + // NOTE: This is marked internal for now. It could be this can be consolidated with GetTokenOrStartSignInAsync. + + // If a flow is active, continue that. + string? activeFlowName = UserInSignInFlow(turnState); + bool shouldStartSignIn = _startSignIn != null && await _startSignIn(turnContext, cancellationToken); + + if (shouldStartSignIn || activeFlowName != null) + { + if (activeFlowName == null) + { + // Auth flow hasn't start yet. + activeFlowName = flowName ?? Default; + } + + // Get token or start flow for specified flow. + SignInResponse response = await _dispatcher.SignUserInAsync(turnContext, activeFlowName, cancellationToken).ConfigureAwait(false); + + if (response.Status == SignInStatus.Pending) + { + // Bank the Activity so it can be executed after sign in is complete. + SetSingInContinuationActivity(turnContext, turnState); + + // Requires user action, save state and stop processing current activity. Done with this turn. + SetActiveFlow(turnState, activeFlowName); + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + return true; + } + + // An InvalidActivity is expected, but anything else is a hard error and the flow is cancelled. + if (response.Status == SignInStatus.Error && response.Cause != AuthExceptionReason.InvalidActivity) + { + // Clear user auth state + await _dispatcher.ResetStateAsync(turnContext, activeFlowName, cancellationToken).ConfigureAwait(false); + DeleteActiveFlow(turnState); + + var signInContinuation = DeleteSingInContinuationActivity(turnState); + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + + if (IsSignInCompletionEvent(signInContinuation)) + { + signInContinuation.Value = new SignInEventValue() { FlowName = flowName, Response = response }; + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + await _app.Options.Adapter.ProcessProactiveAsync(turnContext.Identity, signInContinuation, _app, cancellationToken).ConfigureAwait(false); + return true; + } + + if (_options.SignInFailedMessage == null) + { + throw response.Error; + } + + await turnContext.SendActivitiesAsync(_options.SignInFailedMessage(activeFlowName, response), cancellationToken).ConfigureAwait(false); + return true; + } + + if (response.Status == SignInStatus.Complete) + { + DeleteActiveFlow(turnState); + SetTokenInState(turnState, activeFlowName, response.TokenResponse.Token); + // TODO: should probably call "_authentication.Get(flowName).ResetState?", but is this safe? + + var signInContinuation = DeleteSingInContinuationActivity(turnState); + if (signInContinuation != null) + { + if (IsSignInCompletionEvent(signInContinuation)) + { + // This is to continue a manual signin completion. + // Since we could be handling an Invoke in this turn, and ITurnContext.Activity is readonly, + // we need to continue the conversation in a different turn with the SignInCompletion Event. + // This is handled by the route added in AddManualSignInCompletionHandler(). + signInContinuation.Value = new SignInEventValue() { FlowName = activeFlowName, Response = response }; + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + await _app.Options.Adapter.ProcessProactiveAsync(turnContext.Identity, signInContinuation, _app, cancellationToken).ConfigureAwait(false); + return true; + } + + // If the current activity matches the one used to trigger sign in, then + // this is because the user received a token that didn't involve a multi-turn + // flow. No further action needed. + if (!ProtocolJsonSerializer.Equals(signInContinuation, turnContext.Activity)) + { + // Since we could be handling an Invoke in this turn, and ITurnContext.Activity is readonly, + // we need to continue the conversation in a different turn with the original Activity that triggered sign in. + await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false); + await _app.Options.Adapter.ProcessProactiveAsync(turnContext.Identity, signInContinuation, _app, cancellationToken).ConfigureAwait(false); + return true; + } + } + } + + // If we got this far, fall through to normal Activity route handling. + if (_options.CompletedMessage != null) + { + await turnContext.SendActivitiesAsync(_options.CompletedMessage(activeFlowName, response), cancellationToken).ConfigureAwait(false); + } + } + + return false; + } + + public async Task SignOutUserAsync(ITurnContext turnContext, ITurnState turnState, string? flowName = null, CancellationToken cancellationToken = default) + { + var flow = flowName ?? Default; + await _dispatcher.SignOutUserAsync(turnContext, flow, cancellationToken).ConfigureAwait(false); + DeleteTokenFromState(turnState, flow); + } + + public async Task ResetStateAsync(ITurnContext turnContext, ITurnState turnState, string flowName = null, CancellationToken cancellationToken = default) + { + await _dispatcher.ResetStateAsync(turnContext, flowName ?? Default, cancellationToken).ConfigureAwait(false); + DeleteActiveFlow(turnState); + DeleteTokenFromState(turnState, flowName ?? _options.Default); + } + + + /// + /// The handler function is called when the user has successfully signed in + /// + /// + /// This is only used for manual user authentication. The Auto Sign In will continue the turn with the original user message. + /// + /// The handler function to call when the user has successfully signed in + /// The class itself for chaining purpose + public void OnUserSignInSuccess(Func handler) + { + _userSignInSuccessHandler = handler; + } + + /// + /// The handler function is called when the user sign in flow fails + /// + /// + /// This is only used for manual user authentication. The Auto Sign In will end the turn with and optional error message + /// or exception. + /// + /// The handler function to call when the user failed to signed in + /// The class itself for chaining purpose + public void OnUserSignInFailure(Func handler) + { + _userSignInFailureHandler = handler; + } + + private void AddManualSignInCompletionHandler() + { + RouteSelectorAsync routeSelector = (context, _) => Task.FromResult + ( + string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(context.Activity?.Name, "application/vnd.microsoft.SignInComplete") + ); + RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => + { + var signInCompletion = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value); + if (signInCompletion.Response.Status == SignInStatus.Complete && _userSignInSuccessHandler != null) + { + SetTokenInState(turnState, signInCompletion.FlowName, signInCompletion.Response.TokenResponse.Token); + await _userSignInSuccessHandler(turnContext, turnState, signInCompletion.FlowName, signInCompletion.Response.TokenResponse, cancellationToken).ConfigureAwait(false); + } + else if (_userSignInFailureHandler != null) + { + await _userSignInFailureHandler(turnContext, turnState, signInCompletion.FlowName, signInCompletion.Response, cancellationToken).ConfigureAwait(false); + } + }; + + _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + } + + public static bool IsSignInCompletionEvent(IActivity activity) + { + return string.Equals(activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) + && string.Equals(activity?.Name, SignInCompletionEventName); + + } + + /// + /// If the user is signed in, get the access token. If not, triggers the sign in flow for the provided user authentication flow name + /// and returns. In this case, the bot should end the turn until the sign in flow is completed. In all cases, the completionHandler is + /// invoked with the SignInResponse. + /// + /// + /// The OnUserSignInSuccess and OnUserSignInFailure should be set prior to using manual sign in. + /// + /// The turn context. + /// + /// The name of the authentication setting. + /// The cancellation token. + /// If a flow is already active. + public async Task GetTokenOrStartSignInAsync(ITurnContext turnContext, ITurnState turnState, string flowName, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(turnContext); + ArgumentNullException.ThrowIfNull(turnState); + ArgumentException.ThrowIfNullOrWhiteSpace(flowName); + + // Only one active flow allowed + if (!string.IsNullOrEmpty(UserInSignInFlow(turnState))) + { + DeleteActiveFlow(turnState); + throw new InvalidOperationException("Invalid sign in flow state. Cannot start sign in when already started"); + } + + SignInResponse response = await _dispatcher.SignUserInAsync(turnContext, flowName, cancellationToken).ConfigureAwait(false); + + if (response.Status == SignInStatus.Pending) + { + SetActiveFlow(turnState, flowName); + + var continuationActivity = new Activity() + { + Type = ActivityTypes.Event, + Name = SignInCompletionEventName, + ServiceUrl = turnContext.Activity.ServiceUrl, + ChannelId = turnContext.Activity.ChannelId, + ChannelData = turnContext.Activity.ChannelData, + }; + continuationActivity.ApplyConversationReference(turnContext.Activity.GetConversationReference(), isIncoming: true); + + // This Activity will be used to trigger the handler added by `OnSignInComplete`. + // The Activity.Value will be updated in SignUserInAsync when flow is complete/error. + SetSingInContinuationActivity(turnState, continuationActivity); + + + return; + } + + if (response.Status == SignInStatus.Error) + { + if (_userSignInFailureHandler != null) + { + await _userSignInFailureHandler(turnContext, turnState, flowName, response, cancellationToken).ConfigureAwait(false); + } + else + { + string message = response.Error!.ToString(); + + // TODO: the current activity shouldn't trigger an error in the case of manual signin. Problem is, the flow impls + // currently fail all but specific activities. + if (response.Cause == AuthExceptionReason.InvalidActivity) + { + message = $"User is not signed in and cannot start sign in flow for this activity: {response.Error}"; + } + + throw new InvalidOperationException($"Error occurred while trying to authenticate user: {message}, flow: {flowName}"); + } + } + + // Call the handler immediately if the user was already signed in. + if (response.Status == SignInStatus.Complete) + { + DeleteActiveFlow(turnState); + SetTokenInState(turnState, flowName, response.TokenResponse.Token); + + // call the handler directly + if (_userSignInSuccessHandler != null) + { + await _userSignInSuccessHandler(turnContext, turnState, flowName, response.TokenResponse, cancellationToken).ConfigureAwait(false); + } + } + } + + /// + /// Set token in state + /// + /// The turn state + /// The name of token + /// The value of token + private static void SetTokenInState(ITurnState state, string name, string token) + { + state.Temp.AuthTokens[name] = token; + } + + /// + /// Delete token from turn state + /// + /// The turn state + /// The name of token + private static void DeleteTokenFromState(ITurnState turnState, string name) + { + if (turnState.Temp.AuthTokens != null && turnState.Temp.AuthTokens.ContainsKey(name)) + { + turnState.Temp.AuthTokens.Remove(name); + } + } + + /// + /// Determines if the user is in the sign in flow. + /// + /// The turn state. + /// The connection setting name if the user is in sign in flow. Otherwise null. + private static string? UserInSignInFlow(ITurnState turnState) + { + string? value = turnState.User.GetValue(IS_SIGNED_IN_KEY); + + if (value == string.Empty || value == null) + { + return null; + } + + return value; + } + + /// + /// Update the turn state to indicate the user is in the sign in flow by providing the authentication setting name used. + /// + /// The turn state. + /// The connection setting name defined when configuring the authentication options within the application class. + private static void SetActiveFlow(ITurnState turnState, string flowName) + { + turnState.User.SetValue(IS_SIGNED_IN_KEY, flowName); + } + + /// + /// Delete the user in sign in flow state from the turn state. + /// + /// The turn state. + private static void DeleteActiveFlow(ITurnState turnState) + { + if (turnState.User.HasValue(IS_SIGNED_IN_KEY)) + { + turnState.User.DeleteValue(IS_SIGNED_IN_KEY); + } + } + + private static void SetSingInContinuationActivity(ITurnContext turnContext, ITurnState turnState) + { + SetSingInContinuationActivity(turnState, turnContext.Activity); + } + + private static void SetSingInContinuationActivity(ITurnState turnState, IActivity activity) + { + turnState.User.SetValue(SIGNIN_ACTIVITY_KEY, activity); + } + + private static IActivity DeleteSingInContinuationActivity(ITurnState turnState) + { + var activity = turnState.User.GetValue(SIGNIN_ACTIVITY_KEY); + if (activity != null) + { + turnState.User.DeleteValue(SIGNIN_ACTIVITY_KEY); + } + return activity; + } + } + + class SignInEventValue + { + public string FlowName { get; set; } + public SignInResponse Response { get; set; } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationOptions.cs similarity index 54% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationOptions.cs index b22c4860..127dad39 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthenticationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationOptions.cs @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.UserAuth; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; using System; @@ -9,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication +namespace Microsoft.Agents.BotBuilder.App.UserAuth { /// /// Function for determining whether authentication should be enabled for an activity. @@ -23,13 +24,25 @@ namespace Microsoft.Agents.BotBuilder.App.Authentication /// /// Options for authentication. /// - public class AuthenticationOptions + public class UserAuthenticationOptions { + public static SelectorAsync AutoSignInOn = (context, cancellationToken) => Task.FromResult(true); + public static SelectorAsync AutoSignInOff = (context, cancellationToken) => Task.FromResult(UserAuthenticationFeature.IsSignInCompletionEvent(context.Activity)); + /// /// The authentication settings to sign-in and sign-out users. /// Key uniquely identifies each authentication. /// - internal Dictionary _authenticationSettings { get; set; } + public List Handlers = []; + + public UserAuthenticationOptions(params IUserAuthentication[] flowHandlers) + { + foreach (var flowHandler in flowHandlers) + { + AddAuthentication(flowHandler); + } + } + /// /// Describes the authentication class the bot should use if the user does not specify a authentication class name. @@ -40,6 +53,7 @@ public class AuthenticationOptions /// /// The IStorage used by all IAuthentication instances. /// + [Obsolete("User Handlers property")] public IStorage Storage { get; set; } /// @@ -49,29 +63,44 @@ public class AuthenticationOptions /// public SelectorAsync? AutoSignIn { get; set; } + /// + /// Optional sign in completion message. This is only used if the is not set. + /// public Func CompletedMessage { get; set; } - public Func SignInFailedMessage { get; set; } = (string flowName, SignInResponse response) => + /// + /// Optional sign in failure message. This is only used if the is not set. + /// + public Func SignInFailedMessage { get; set; } = (flowName, response) => [MessageFactory.Text(string.Format("Sign in for '{0}' completed without a token. Status={1}", flowName, response.Cause))]; /// /// Configures the options to add an OAuth authentication setting. /// - /// The authentication name. - /// The OAuth settings + /// The user authentication handler name. + /// The OAuth settings /// The object for chaining purposes. - public AuthenticationOptions AddAuthentication(string name, OAuthSettings oauthSettings) + [Obsolete("Use AddAuthentication(IUserAuthentication) or UserAuthenticationOptions(params IUserAuthentication[] flowHandlers)")] + public UserAuthenticationOptions AddAuthentication(string name, OAuthSettings settings) { - _authenticationSettings.Add(name, oauthSettings); + ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name)); + ArgumentNullException.ThrowIfNull(nameof(settings)); + + if (settings is OAuthSettings oauthSettings) + { + Handlers.Add(new OAuthAuthentication(name, oauthSettings, Storage)); + } + else + { + throw new ArgumentException($"Unknow OAuthSettings type {settings.GetType().ToString()}"); + } return this; } - /// - /// The authentication options constructor. - /// - public AuthenticationOptions() + public UserAuthenticationOptions AddAuthentication(IUserAuthentication flowHandler) { - _authenticationSettings = []; + Handlers.Add(flowHandler); + return this; } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/AuthException.cs similarity index 96% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/AuthException.cs index d3418929..babfe073 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/AuthException.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/AuthException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.BotBuilder.App.Authentication +namespace Microsoft.Agents.BotBuilder.UserAuth { /// /// Cause of user authentication exception. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs similarity index 94% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs index 70287648..8cc5a328 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/IAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs @@ -5,13 +5,15 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication +namespace Microsoft.Agents.BotBuilder.UserAuth { /// /// Handles user sign-in and sign-out. /// - public interface IAuthentication + public interface IUserAuthentication { + string Name { get; } + /// /// Signs in a user. /// This method will be called automatically by the Application class. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthenticationDispatcher.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthenticationDispatcher.cs new file mode 100644 index 00000000..1e33f4c7 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthenticationDispatcher.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.UserAuth +{ + public interface IUserAuthenticationDispatcher + { + IUserAuthentication Default { get; } + + /// + /// Get an authentication class via name + /// + /// The name of the user authentication flow + /// The user authentication handler + /// When cannot find the class with given name + IUserAuthentication Get(string flowName); + + /// + /// + /// + /// The turn context + /// The name of the authentication handler to use. If null, the default handler name is used. + /// The cancellation token + /// + Task ResetStateAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default); + + /// + /// Sign in a user. + /// + /// + /// On success, this will put the token in ITurnState.Temp.AuthTokens[settingName]. + /// + /// The turn context + /// The name of the authentication handler to use. If null, the default handler name is used. + /// The cancellation token + /// The sign in status + Task SignUserInAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default); + + /// + /// Signs out a user. + /// + /// The turn context + /// Optional. The name of the authentication handler to use. If not specified, the default handler name is used. + /// The cancellation token + Task SignOutUserAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInResponse.cs similarity index 88% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInResponse.cs index 4e5e336d..ccc70bf2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInResponse.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInResponse.cs @@ -4,7 +4,7 @@ using Microsoft.Agents.Core.Models; using System; -namespace Microsoft.Agents.BotBuilder.App.Authentication +namespace Microsoft.Agents.BotBuilder.UserAuth { /// /// The sign-in response @@ -31,7 +31,7 @@ public class SignInResponse(SignInStatus status) public AuthExceptionReason? Cause { get; set; } /// - /// The token resonse. Only available when sign-in status is Complete. + /// The token response. Only available when sign-in status is Complete. /// public TokenResponse TokenResponse { get; set; } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInStatus.cs similarity index 90% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInStatus.cs index 8106f844..61d8e3af 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/SignInStatus.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/SignInStatus.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Agents.BotBuilder.App.Authentication +namespace Microsoft.Agents.BotBuilder.UserAuth { /// /// The sign-in status diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/DedupeTokenExchange.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/DedupeTokenExchange.cs index 2661640e..1f29abd2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/DedupeTokenExchange.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/DedupeTokenExchange.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Microsoft.Agents.Connector; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { /// /// If the activity name is signin/tokenExchange, this middleware will attempt to @@ -24,7 +24,7 @@ namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService /// specific user login will have an identical Activity.Value.Id. /// /// Only one of these token exchange requests should be processed by the bot. - /// The others return . + /// The others return . /// For a distributed bot in production, this requires a distributed storage /// ensuring only one token exchange is processed. This middleware supports /// CosmosDb storage found in Microsoft.Bot.Builder.Azure, or MemoryStorage for @@ -96,7 +96,7 @@ private async Task DeduplicatedTokenExchangeIdAsync(ITurnContext turnConte // Create a StoreItem with Etag of the unique 'signin/tokenExchange' request var storeItem = new TokenStoreItem { - ETag = ProtocolJsonSerializer.ToJsonElements(turnContext.Activity.Value)["id"].ToString(), + ETag = turnContext.Activity.Value.ToJsonElements()["id"].ToString(), }; var storeItems = new Dictionary { { TokenStoreItem.GetStorageKey(turnContext), storeItem } }; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthAuthentication.cs similarity index 94% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthAuthentication.cs index 986b456f..290595cb 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthAuthentication.cs @@ -3,15 +3,16 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Storage; +using System; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { /// /// Handles user authentication using The Azure Bot Token Service. /// - public class OAuthAuthentication : IAuthentication + public class OAuthAuthentication : IUserAuthentication { private readonly OAuthSettings _settings; //private readonly OAuthMessageExtensionsAuthentication? _messageExtensionAuth; @@ -25,6 +26,7 @@ public class OAuthAuthentication : IAuthentication /// The storage to use. public OAuthAuthentication(string name, OAuthSettings settings, IStorage storage) : this(settings, new OAuthBotAuthentication(name, settings, storage)) { + Name = name ?? throw new ArgumentNullException(nameof(name)); } /// @@ -40,6 +42,8 @@ internal OAuthAuthentication(OAuthSettings settings, OAuthBotAuthentication botA _botAuthentication = botAuthentication; } + public string Name { get; private set; } + /// /// Check if the user is signed, if they are then return the token. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthBotAuthentication.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthBotAuthentication.cs index 4726f14a..aa508f21 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthBotAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthBotAuthentication.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { /// /// Base class for bot authentication that handles common logic. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthFlow.cs similarity index 99% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthFlow.cs index fd9812f7..037b9b9e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthFlow.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthFlow.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { /// /// Creates a new prompt that asks the user to sign in using the Azure Bot Token Service. diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs similarity index 92% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs index 20d5b3a3..980b771e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/OAuthSettings.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { /// /// The settings for OAuthAuthentication. @@ -41,7 +41,7 @@ public class OAuthSettings /// Default is . /// /// The number of milliseconds the prompt waits for the user to authenticate. - public int? Timeout { get; set; } = (int) DefaultTimeoutValue.TotalMilliseconds; + public int? Timeout { get; set; } = (int)DefaultTimeoutValue.TotalMilliseconds; /// /// Gets or sets a value indicating whether the should end upon @@ -53,7 +53,7 @@ public class OAuthSettings /// /// True if the should automatically end upon receiving /// an invalid message. - public bool EndOnInvalidMessage { get; set; } + public bool EndOnInvalidMessage { get; set; } = true; /// /// Gets or sets an optional boolean value to force the display of a Sign In link overriding diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs similarity index 97% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs index ccaba06f..bf654efa 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Authentication/TokenService/UserTokenClientWrapper.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Agents.BotBuilder.App.Authentication.TokenService +namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { public class UserTokenClientWrapper { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/UserAuthenticationDispatcher.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/UserAuthenticationDispatcher.cs new file mode 100644 index 00000000..3d728fc5 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/UserAuthenticationDispatcher.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App.UserAuth; +using Microsoft.Agents.Core.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.UserAuth +{ + /// + /// The user authentication manager + /// + public class UserAuthenticationDispatcher : IUserAuthenticationDispatcher + { + private readonly Dictionary _userAuthHandlers = []; + + /// + /// + /// + /// + public UserAuthenticationDispatcher(IUserAuthentication[] userAuthHandlers) + { + ArgumentNullException.ThrowIfNull(nameof(userAuthHandlers)); + + foreach(var authenticator in userAuthHandlers) + { + _userAuthHandlers.Add(authenticator.Name, authenticator); + } + } + + public IUserAuthentication Default => _userAuthHandlers.Count > 0 ? _userAuthHandlers.First().Value : throw new InvalidOperationException("No IUserAuthentication have been defined."); + + /// + /// Creates a new instance of the class + /// + /// The application. + /// The authentication options + /// The storage to use. + /// Throws when the options does not contain authentication handlers + public UserAuthenticationDispatcher(UserAuthenticationOptions options) + { + ArgumentNullException.ThrowIfNull(nameof(options)); + + if (options.Handlers.Count == 0) + { + throw new ArgumentException("Authentications setting is empty"); + } + + foreach(var authenticator in options.Handlers) + { + _userAuthHandlers.Add(authenticator.Name, authenticator); + } + } + + /// + public async Task SignUserInAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(nameof(turnContext)); + + IUserAuthentication auth = Get(flowName); + TokenResponse token; + try + { + token = await auth.SignInUserAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + SignInResponse newResponse = new(SignInStatus.Error) + { + Error = ex, + Cause = AuthExceptionReason.Other + }; + if (ex is AuthException authEx) + { + newResponse.Cause = authEx.Cause; + } + + return newResponse; + } + + if (token != null) + { + return new SignInResponse(SignInStatus.Complete) + { + TokenResponse = token + }; + } + + return new SignInResponse(SignInStatus.Pending); + } + + /// + public async Task SignOutUserAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(nameof(turnContext)); + + IUserAuthentication auth = Get(flowName); + await auth.SignOutUserAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task ResetStateAsync(ITurnContext turnContext, string flowName, CancellationToken cancellationToken = default) + { + await Get(flowName).ResetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); + } + + /// + public IUserAuthentication Get(string name) + { + if (string.IsNullOrEmpty(name)) + { + return Default; + } + + if (_userAuthHandlers.TryGetValue(name, out IUserAuthentication? value)) + { + return value; + } + + throw new InvalidOperationException($"Could not find user authentication handler '{name}'."); + } + } +} diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index d6c15f81..1b670ff7 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -4,7 +4,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs index 62dbaafe..8e62c462 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs @@ -3,7 +3,6 @@ using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs index 83d48a02..f3e3621b 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs @@ -14,7 +14,6 @@ using Microsoft.Agents.Extensions.Teams.App.Meetings; using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; using Microsoft.Agents.Extensions.Teams.App.TaskModules; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; namespace Microsoft.Agents.Extensions.Teams.App { diff --git a/src/samples/Application/AuthenticationBot/Program.cs b/src/samples/Application/AuthenticationBot/Program.cs index 7f6377c2..9767c114 100644 --- a/src/samples/Application/AuthenticationBot/Program.cs +++ b/src/samples/Application/AuthenticationBot/Program.cs @@ -4,9 +4,9 @@ using AuthenticationBot; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; -using Microsoft.Agents.BotBuilder.App.Authentication; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; +using Microsoft.Agents.BotBuilder.App.UserAuth; using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; @@ -32,30 +32,54 @@ builder.AddBot((sp) => { var adapter = sp.GetService(); + var storage = sp.GetService(); - var authOptions = new AuthenticationOptions() + var authOptions = new UserAuthenticationOptions() { - Storage = sp.GetService() + // Auto-SignIn will use this OAuth flow + Default = "graph", + + AutoSignIn = UserAuthenticationOptions.AutoSignInOn, //UserAuthenticationOptions.AutoSignInOff, + + Handlers = + [ + new OAuthAuthentication( + "graph", + new OAuthSettings() + { + ConnectionName = builder.Configuration["ConnectionName"] + }, + storage)] }; - authOptions.AddAuthentication("graph", new OAuthSettings() - { - ConnectionName = builder.Configuration["ConnectionName"], - Title = "Sign In", - Text = "Please sign in to use the bot.", - EndOnInvalidMessage = true, - EnableSso = true, - }); var appOptions = new ApplicationOptions() { Adapter = adapter, - StartTypingTimer = false, - Authentication = authOptions, + StartTypingTimer = true, + UserAuthentication = authOptions, TurnStateFactory = () => sp.GetService() }; var app = new Application(appOptions); + app.Authentication.OnUserSignInSuccess(async (turnContext, turnState, flowName, tokenResponse, cancellationToken) => + { + await turnContext.SendActivityAsync($"Successfully logged in to '{flowName}'", cancellationToken: cancellationToken); + await turnContext.SendActivityAsync($"Token string length: {tokenResponse.Token.Length}", cancellationToken: cancellationToken); + }); + + app.Authentication.OnUserSignInFailure(async (turnContext, turnState, flowName, response, cancellationToken) => + { + // Failed to login + await turnContext.SendActivityAsync($"Failed to login to '{flowName}'", cancellationToken: cancellationToken); + await turnContext.SendActivityAsync($"Error message: {response.Error.Message}", cancellationToken: cancellationToken); + }); + + app.OnMessage("/signin", async (turnContext, turnState, cancellationToken) => + { + await app.Authentication.GetTokenOrStartSignInAsync(turnContext, turnState, "graph", cancellationToken); + }); + // Listen for user to say "/reset" and then delete state app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => { @@ -64,7 +88,7 @@ await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); }); - // Listen for user to say "/sigout" and then delete cached token + // Listen for user to say "/signout" and then delete cached token app.OnMessage("/signout", async (turnContext, turnState, cancellationToken) => { await app.Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs index 040d4c23..d18b6c3c 100644 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -6,10 +6,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.App.Authentication; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.Compat; using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; using Microsoft.Agents.Core.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs new file mode 100644 index 00000000..bef23c85 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs @@ -0,0 +1,19 @@ + + +using Microsoft.Agents.BotBuilder.App; +using System; + +namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils +{ + public class TestApplication : Application + { + public TestApplication(TestApplicationOptions options) : base(options) + { + ArgumentNullException.ThrowIfNull(options); + + options.StartTypingTimer = false; + } + } + + public class TestApplicationOptions : ApplicationOptions { } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs new file mode 100644 index 00000000..98de81a9 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs @@ -0,0 +1,13 @@ + +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.UserAuth; + +namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils +{ + internal sealed class TestUserAuthenticationFeature : UserAuthenticationFeature + { + public TestUserAuthenticationFeature(Application app, UserAuthenticationOptions options) : base(app, options) + { + } + } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs new file mode 100644 index 00000000..8dbfa323 --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs @@ -0,0 +1,194 @@ + +using Microsoft.Agents.BotBuilder.App.UserAuth; +using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; +using Microsoft.Agents.BotBuilder.UserAuth; +using Microsoft.Agents.Core.Models; +using Moq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.BotBuilder.Tests.App +{ + public class UserAuthenticationFeatureTests + { + private const string GraphName = "graph"; + private const string SharePointName = "sharepoint"; + private const string GraphToken = "graph token"; + private const string SharePointToken = "sharePoint token"; + private Mock MockGraph; + private Mock MockSharePoint; + + public UserAuthenticationFeatureTests() + { + MockGraph = new Mock(); + MockGraph + .Setup(e => e.SignInUserAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new TokenResponse() { Token = GraphToken })); + MockGraph + .Setup(e => e.Name) + .Returns(GraphName); + + MockSharePoint = new Mock(); + MockSharePoint + .Setup(e => e.SignInUserAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new TokenResponse() { Token = SharePointToken })); + MockSharePoint + .Setup(e => e.Name) + .Returns(SharePointName); + } + + [Fact] + public async Task Test_AutoSignIn_Default() + { + // arrange + var app = new TestApplication(new TestApplicationOptions()); + var options = new UserAuthenticationOptions(); + options.Handlers = + [ + MockGraph.Object, + MockSharePoint.Object, + ]; + + var authManager = new TestUserAuthenticationFeature(app, options); + var turnContext = MockTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + // act + var response = await authManager.SignUserInAsync(turnContext, turnState); + + // assert + Assert.False(response); + Assert.True(turnState.Temp.AuthTokens.ContainsKey(GraphName)); + Assert.Equal(GraphToken, turnState.Temp.AuthTokens[GraphName]); + } + + [Fact] + public async Task Test_AutoSignIn_Named() + { + // arrange + var app = new TestApplication(new TestApplicationOptions()); + var options = new UserAuthenticationOptions + { + Handlers = + [ + MockGraph.Object, + MockSharePoint.Object, + ] + }; + var authManager = new TestUserAuthenticationFeature(app, options); + var turnContext = MockTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + // act + var response = await authManager.SignUserInAsync(turnContext, turnState, SharePointName); + + // assert + Assert.False(response); + Assert.True(turnState.Temp.AuthTokens.ContainsKey(SharePointName)); + Assert.Equal(SharePointToken, turnState.Temp.AuthTokens[SharePointName]); + } + + [Fact] + public async Task Test_AutoSignIn_Pending() + { + MockGraph + .Setup(e => e.SignInUserAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult((TokenResponse) null)); + + var app = new TestApplication(new TestApplicationOptions()); + var options = new UserAuthenticationOptions + { + Handlers = + [ + MockGraph.Object + ] + }; + var authManager = new TestUserAuthenticationFeature(app, options); + var turnContext = MockTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + + // act + var response = await authManager.SignUserInAsync(turnContext, turnState); + + // assert + Assert.True(response); + } + + [Fact] + public async Task Test_SignOut_DefaultHandler() + { + // arrange + var app = new TestApplication(new TestApplicationOptions()); + var options = new UserAuthenticationOptions + { + Handlers = + [ + MockGraph.Object, + MockSharePoint.Object, + ] + }; + + var authManager = new TestUserAuthenticationFeature(app, options); + var turnContext = MockTurnContext(); + + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.Temp.AuthTokens = new Dictionary() + { + {GraphName, "graph token" }, + {SharePointName, "sharepoint token" } + }; + + // act + await authManager.SignOutUserAsync(turnContext, turnState); + + // assert + Assert.False(turnState.Temp.AuthTokens.ContainsKey(GraphName)); + Assert.True(turnState.Temp.AuthTokens.ContainsKey(SharePointName)); + } + + [Fact] + public async Task Test_SignOut_SpecificHandler() + { + // arrange + var app = new TestApplication(new TestApplicationOptions()); + var options = new UserAuthenticationOptions + { + Handlers = + [ + MockGraph.Object, + MockSharePoint.Object, + ] + }; + var authManager = new TestUserAuthenticationFeature(app, options); + var turnContext = MockTurnContext(); + var turnState = await TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); + turnState.Temp.AuthTokens = new Dictionary() + { + {GraphName, "graph token" }, + {SharePointName, "sharepoint token" } + }; + + // act + await authManager.SignOutUserAsync(turnContext, turnState, SharePointName); + + // assert + Assert.False(turnState.Temp.AuthTokens.ContainsKey(SharePointName)); + Assert.True(turnState.Temp.AuthTokens.ContainsKey(GraphName)); + } + + private static TurnContext MockTurnContext() + { + return new TurnContext(new SimpleAdapter(), new Activity() + { + Type = ActivityTypes.Message, + Recipient = new() { Id = "recipientId" }, + Conversation = new() { Id = "conversationId" }, + From = new() { Id = "fromId" }, + ChannelId = "channelId", + }); + } + } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs index 1d2575d0..bbc0bde6 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/OAuthFlowTests.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Agents.BotBuilder.App.Authentication; -using Microsoft.Agents.BotBuilder.App.Authentication.TokenService; using Microsoft.Agents.BotBuilder.Testing; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; using Microsoft.Agents.Connector; using Microsoft.Agents.Core.Models; using Moq; From 896b8b9182a68baf2340cc3ae6de6826c3aa45e3 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 25 Feb 2025 16:07:04 -0600 Subject: [PATCH 48/60] Defaulting EndOnInvalidMessage to true fails some Dialog OAuth Tests. Reverted. --- .../UserAuth/TokenService/OAuthSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs index 980b771e..a2b4f2ae 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/OAuthSettings.cs @@ -53,7 +53,7 @@ public class OAuthSettings /// /// True if the should automatically end upon receiving /// an invalid message. - public bool EndOnInvalidMessage { get; set; } = true; + public bool EndOnInvalidMessage { get; set; } /// /// Gets or sets an optional boolean value to force the display of a Sign In link overriding From d369f502bd712940517c62dec871545780328440 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 26 Feb 2025 07:24:50 -0600 Subject: [PATCH 49/60] Migrated core samples to Application --- src/Microsoft.Agents.SDK.sln | 186 ++---------------- .../Prompts/OAuthPrompt.cs | 2 +- .../App/UserAuth/UserAuthenticationFeature.cs | 2 +- .../TokenService/UserTokenClientWrapper.cs | 16 +- .../AspNetCore/ServiceCollectionExtensions.cs | 8 +- .../AuthenticationBot.csproj | 18 -- .../AuthenticationBot/BotController.cs | 25 --- .../Application/AuthenticationBot/Program.cs | 135 ------------- .../Application/AuthenticationBot/README.md | 97 --------- .../AuthenticationBot/StateExtensions.cs | 27 --- .../appManifest/manifest.json | 48 ----- .../AuthenticationBot/appsettings.json | 37 ---- .../.gitignore | 25 --- .../ActivityHandlers.cs | 118 ----------- .../Config.cs | 8 - .../Controllers/BotController.cs | 23 --- .../Model/CardPackage.cs | 43 ---- .../Model/Package.cs | 29 --- .../Program.cs | 50 ----- .../README.md | 31 --- .../Resources/PackageCard.json | 55 ------ .../SearchCommand.csproj | 38 ---- .../SearchCommand.sln | 27 --- .../appPackage/color.png | Bin 1066 -> 0 bytes .../appPackage/manifest.json | 71 ------- .../appPackage/outline.png | Bin 249 -> 0 bytes .../appsettings.json | 38 ---- .../assets/card.png | Bin 76643 -> 0 bytes .../assets/search.png | Bin 61027 -> 0 bytes .../env/.env.dev | 18 -- .../infra/azure.bicep | 74 ------- .../infra/azure.parameters.json | 21 -- .../infra/botRegistration/README.md | 1 - .../infra/botRegistration/azurebot.bicep | 37 ---- .../teamsapp.local.yml | 91 --------- .../teamsapp.yml | 83 -------- .../messaging.echoBot/BotController.cs | 25 --- .../messaging.echoBot/EchoBot.csproj | 28 --- .../Application/messaging.echoBot/EchoBot.sln | 27 --- .../Application/messaging.echoBot/Program.cs | 78 -------- .../Application/messaging.echoBot/README.md | 29 --- .../messaging.echoBot/appsettings.json | 38 ---- src/samples/AuthenticationBot/AuthBot.cs | 140 ------------- .../AuthenticationBot.csproj | 12 ++ src/samples/AuthenticationBot/Program.cs | 117 +++++++++-- .../CopilotStudioClient}/AddTokenHandler.cs | 0 .../ChatConsoleService.cs | 0 .../CopilotStudioClient.csproj} | 3 +- .../CopilotStudioClient}/Program.cs | 0 .../Properties/launchSettings.TEMPLATE.json | 0 .../CopilotStudioClient}/README.md | 0 .../SampleConnectionSettings.cs | 0 .../CopilotStudioClient}/appsettings.json | 0 .../EvalClient/AddTokenHandler.cs | 0 .../EvalClient/Data/Evaluation Dataset.csv | 6 +- .../EvalClient/EvalClient.csproj | 77 ++++---- .../EvalClient/EvalClient.sln | 0 .../EvalClient/EvalClientConfig.cs | 0 .../EvalClient/EvalDataset.cs | 0 .../EvalClient/EvalDatasetCsvMap.cs | 0 .../EvalClient/EvalDatasetResultCsvMap.cs | 0 .../EvalClient/EvaluationService.cs | 0 .../EvalClient/Program.cs | 0 .../EvalClient/README.md | 0 .../EvalClient/appsettings.json | 0 .../CopilotStudioBot.cs | 57 ------ src/samples/CopilotStudioEchoSkill/Program.cs | 62 +++++- src/samples/CopilotStudioEchoSkill/README.md | 4 +- src/samples/EchoBot/MyBot.cs | 36 ---- src/samples/EchoBot/Program.cs | 50 ++++- src/samples/EchoBot/README.md | 32 ++- .../StateExtensions.cs | 0 src/samples/EchoBot/appsettings.json | 7 +- .../WeatherBot/Agents/WeatherForecastAgent.cs | 12 +- .../SemanticKernel/WeatherBot/MyBot.cs | 39 ++-- .../SemanticKernel/WeatherBot/Program.cs | 12 ++ .../appPackage/color.png | Bin 3415 -> 0 bytes .../appPackage/outline.png | Bin 407 -> 0 bytes .../{ => ToMigrate}/BotToBot/.gitignore | 0 .../BotToBot/Bot1/.config/dotnet-tools.json | 0 .../{ => ToMigrate}/BotToBot/Bot1/Bot1.csproj | 0 .../Bot1/BotHostAdapterWithErrorHandler.cs | 0 .../BotToBot/Bot1/Bots/Bot1.cs | 0 .../Controllers/Bot2ResponseController.cs | 0 .../Bot1/Controllers/BotController.cs | 0 .../{ => ToMigrate}/BotToBot/Bot1/Program.cs | 0 .../Bot1/Properties/launchSettings.json | 0 .../{ => ToMigrate}/BotToBot/Bot1/README.md | 0 .../BotToBot/Bot1/appsettings.json | 0 .../BotToBot/Bot1/wwwroot/default.htm | 0 .../{ => ToMigrate}/BotToBot/Bot2/Bot2.csproj | 0 .../Bot2/BotAdapterWithErrorHandler.cs | 0 .../BotToBot/Bot2/Bots/Bot2.cs | 0 .../Bot2/Controllers/BotController.cs | 0 .../{ => ToMigrate}/BotToBot/Bot2/Program.cs | 0 .../Bot2/Properties/launchSettings.json | 0 .../{ => ToMigrate}/BotToBot/Bot2/README.md | 0 .../BotToBot/Bot2/appsettings.json | 0 .../BotToBot/Bot2/wwwroot/default.htm | 0 .../manifest/echobot-manifest-1.0.json | 0 .../{ => ToMigrate}/BotToBot/README.md | 0 .../BotToBot/SimpleBotToBot.sln | 0 .../AdaptiveCardActions.csproj | 0 .../Bots/AdaptiveCardActionsBot.cs | 0 .../Cards/AdaptiveCardActions.json | 0 .../Cards/SuggestedActions.json | 0 .../Cards/ToggleVisibleCard.json | 0 .../Controllers/BotController.cs | 0 .../AdaptiveCardActions/Images/1.Install.png | Bin .../Images/10.ToggleVisibiliyCard.png | Bin .../Images/11.VisibleOnClick.png | Bin .../Images/2.WelcomeMessage.png | Bin .../AdaptiveCardActions/Images/3.Red.png | Bin .../AdaptiveCardActions/Images/4.Green.png | Bin .../AdaptiveCardActions/Images/5.Blue.png | Bin .../Images/6.CardActions.png | Bin .../Images/7.ActionSubmit.png | Bin .../Images/8.ActionShowCard.png | Bin .../Images/AdaptiveCardActions.gif | Bin .../Teams/AdaptiveCardActions/Program.cs | 0 .../Teams/AdaptiveCardActions/README.md | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../AdaptiveCardActions/appsettings.json | 0 .../Bots/TeamsConversationBot.cs | 0 .../Controllers/BotController.cs | 0 .../ConversationBot/ConversationBot.csproj | 0 .../Teams/ConversationBot/Program.cs | 0 .../Teams/ConversationBot/README.md | 0 .../Resources/UserMentionCardTemplate.json | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../ConversationBot/appManifest/manifest.json | 0 .../Teams/ConversationBot/appsettings.json | 0 .../LinkUnfurling/AppManifest/icon-color.png | Bin .../AppManifest/icon-outline.png | Bin .../LinkUnfurling/AppManifest/manifest.json | 0 .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 0 .../Controllers/BotController.cs | 0 .../Teams/LinkUnfurling/Images/Add-App.png | Bin .../LinkUnfurling/Images/Link-Unfurling.png | Bin .../LinkUnfurling/Images/OpenAppIcon.png | Bin .../LinkUnfurling/Images/OpenNewMail.png | Bin .../Images/SearchInExtension.png | Bin .../Images/msgext-link-unfurling.gif | Bin .../Teams/LinkUnfurling/LinkUnfurling.csproj | 0 .../Teams/LinkUnfurling/Program.cs | 0 .../Teams/LinkUnfurling/README.md | 0 .../Teams/LinkUnfurling/appsettings.json | 0 .../Bots/MeetingContextBot.cs | 0 .../Controllers/BotController.cs | 0 .../Meeting-Context-App/Images/1.setup.png | Bin .../Images/2.add_to_meeting.png | Bin .../Images/3.tab_configuration.png | Bin .../Images/4.tab_context_details.png | Bin .../Meeting-Context-App/Images/AddToChat.png | Bin .../Images/Meeting-Details.png | Bin .../Images/MeetingContext.png | Bin .../Images/Participant-Details.png | Bin .../Images/ParticipantContext.png | Bin .../Images/Setup-Tab-Bot.png | Bin .../Meeting-Context-App/Images/Tab-View.png | Bin .../Images/meetingTabContext.png | Bin .../Images/meeting_context_csharp.gif | Bin .../MeetingContextApp.csproj | 0 .../Teams/Meeting-Context-App/Program.cs | 0 .../Teams/Meeting-Context-App/README.md | 0 .../Meeting-Context-App/appPackage}/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage}/outline.png | Bin .../Meeting-Context-App/appsettings.json | 0 .../Bots/InMeetingNotifications.cs | 0 .../Cards/AgendaCard.json | 0 .../Cards/QuestionTemplate.json | 0 .../Cards/SendTargetNotificationCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Home_Page.png | Bin .../Images/3.Send_Meeting_Notification.png | Bin .../Images/4.Option_Card.png | Bin .../Images/5.Output_in_Chat.png | Bin .../Images/6.Card_in_Meeting_Chat.png | Bin .../Images/7.Popup_Window.png | Bin .../Images/MeetingNotification.gif | Bin .../InMeetingNotificationsBot.csproj | 0 .../Models/ActionBase.cs | 0 .../Models/AgendaItem.cs | 0 .../Models/MeetingAgenda.cs | 0 .../Models/MeetingNotification.cs | 0 .../Models/ParticipantDetail.cs | 0 .../Models/PushAgendaAction.cs | 0 .../Models/SubmitFeedback.cs | 0 .../Pages/InMeetingNotificationPage.cshtml | 0 .../Pages/InMeetingNotificationPage.cshtml.cs | 0 .../Pages/SendNotificationPage.cshtml | 0 .../Pages/SendNotificationPage.cshtml.cs | 0 .../Pages/Shared/_Layout.cshtml | 0 .../Pages/_ViewStart.cshtml | 0 .../Teams/Meetings-Notification/Program.cs | 0 .../Teams/Meetings-Notification/README.md | 0 .../Teams/Meetings-Notification/Titles.cs | 0 .../appPackage/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage/outline.png | Bin .../Meetings-Notification/appsettings.json | 0 .../Bots/TeamsMessagingExtensionsSearchBot.cs | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.AddSuccessfully.png | Bin .../Images/3.SelectSample.png | Bin .../Images/4.QueryResults.png | Bin .../Images/5.SentSelectedPackage.png | Bin .../Images/6.SearchQueryResult-2.png | Bin .../Images/msgext-search.gif | Bin .../MessagingExtensionsSearch.csproj | 0 .../MessagingExtensionsSearch/Program.cs | 0 .../Teams/MessagingExtensionsSearch/README.md | 0 .../Resources/RestaurantCard.json | 0 .../Resources/UserMentionCardTemplate.json | 0 .../Resources/connectorCard.json | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../appManifest/manifest.json | 0 .../appsettings.json | 0 .../wwwroot/default.html | 0 .../TaskModule/Bots/TeamsTaskModuleBot.cs | 0 .../TaskModule/Controllers/BotController.cs | 0 .../TaskModule/Helper/AdaptiveCardHelper.cs | 0 .../TaskModule/Helper/ApplicationSettings.cs | 0 .../Teams/TaskModule/Helper/DeeplinkHelper.cs | 0 .../Teams/TaskModule/Helper/UIConstants.cs | 0 .../Teams/TaskModule/Images/1.InstallApp.png | Bin .../Teams/TaskModule/Images/10.GroupChat.png | Bin .../TaskModule/Images/11.GroupDialogs.png | Bin .../TaskModule/Images/12.GroupCustomForm.png | Bin .../TaskModule/Images/13.GroupResults.png | Bin .../Teams/TaskModule/Images/2.Dialogs.png | Bin .../TaskModule/Images/3.AdaptiveCard.png | Bin .../Images/4.ThanksAdaptiveCard.png | Bin .../Teams/TaskModule/Images/5.CustomForm.png | Bin .../Teams/TaskModule/Images/6.Results.png | Bin .../Teams/TaskModule/Images/7.YouTube.png | Bin .../Teams/TaskModule/Images/8.Task.png | Bin .../Teams/TaskModule/Images/9.PowerApp.png | Bin .../TaskModule/Images/Bot_Tab_TaskModule.gif | Bin .../Models/AdaptiveCardTaskFetchValue.cs | 0 .../TaskModule/Models/CardTaskFetchValue.cs | 0 .../Teams/TaskModule/Models/TaskModuleIds.cs | 0 .../Models/TaskModuleResponseFactory.cs | 0 .../Models/TaskModuleUIConstants.cs | 0 .../Teams/TaskModule/Models/UISettings.cs | 0 .../Teams/TaskModule/Pages/Configure.cshtml | 0 .../Teams/TaskModule/Pages/CustomForm.cshtml | 0 .../TaskModule/Pages/CustomForm.cshtml.cs | 0 .../Teams/TaskModule/Pages/HelloWorld.cshtml | 0 .../Teams/TaskModule/Pages/PowerApp.cshtml | 0 .../TaskModule/Pages/Shared/_EmbedPage.cshtml | 0 .../TaskModule/Pages/Shared/_Layout.cshtml | 0 .../Teams/TaskModule/Pages/Tasks.cshtml | 0 .../Teams/TaskModule/Pages/YouTube.cshtml | 0 .../Teams/TaskModule/Pages/_ViewStart.cshtml | 0 .../Teams/TaskModule/Program.cs | 0 .../Teams/TaskModule/README.md | 0 .../Resources/AdaptiveCard_TaskModule.json | 0 .../TaskModule/Resources/adaptiveCard.json | 0 .../Teams/TaskModule/TaskModule.csproj | 0 .../Teams/TaskModule/appManifest}/color.png | Bin .../TaskModule/appManifest/manifest.json | 0 .../Teams/TaskModule/appManifest}/outline.png | Bin .../Teams/TaskModule/appsettings.json | 0 .../Teams/TaskModule/assets/sample.json | 0 .../Teams/TaskModule/wwwroot/css/Site.css | 0 .../Teams/TaskModule/wwwroot/css/custom.css | 0 .../TaskModule/wwwroot/css/msteams-16.css | 0 .../Teams/TaskModule/wwwroot/default.htm | 0 .../Teams/bot-all-cards/BotAllCards.csproj | 0 .../Teams/bot-all-cards/Bots/DialogBot.cs | 0 .../Teams/bot-all-cards/Bots/TeamsBot.cs | 0 .../Teams/bot-all-cards/Cards/AllCards.cs | 0 .../Controllers/BotController.cs | 0 .../Teams/bot-all-cards/Dialogs/MainDialog.cs | 0 .../Teams/bot-all-cards/Images/1.Install.png | Bin .../Images/10.CollectionCard.png | Bin .../bot-all-cards/Images/11.ConnectorCard.png | Bin .../Teams/bot-all-cards/Images/2.Welcome.png | Bin .../bot-all-cards/Images/3.SelectCards.png | Bin .../bot-all-cards/Images/4.AdaptiveCard.png | Bin .../Teams/bot-all-cards/Images/5.HeroCard.png | Bin .../Teams/bot-all-cards/Images/6.OathCard.png | Bin .../bot-all-cards/Images/7.SignInCard.png | Bin .../bot-all-cards/Images/8.ThumbnailCard.png | Bin .../Teams/bot-all-cards/Images/9.ListCard.png | Bin .../Images/AdaptiveCardMedia.png | Bin .../Images/AdaptiveCardMedia2.png | Bin .../bot-all-cards/Images/Authentication.png | Bin .../bot-all-cards/Images/OauthConnection.png | Bin .../bot-all-cards/Images/allBotCardsGif.gif | Bin .../Teams/bot-all-cards/Program.cs | 0 .../Teams/bot-all-cards/README.md | 0 .../bot-all-cards/Resources/adaptiveCard.json | 0 .../Resources/adaptiveCardMedia.json | 0 .../Resources/collectionsCard.json | 0 .../bot-all-cards/Resources/heroCard.json | 0 .../bot-all-cards/Resources/listCard.json | 0 .../Resources/o365ConnectorCard.json | 0 .../Resources/thumbnailCard.json | 0 .../bot-all-cards}/appManifest/color.png | Bin .../bot-all-cards/appManifest/manifest.json | 0 .../bot-all-cards}/appManifest/outline.png | Bin .../Teams/bot-all-cards/appsettings.json | 0 .../Teams/bot-all-cards/wwwroot/default.htm | 0 .../BotConversationSsoQuickstart.csproj | 0 .../Bots/DialogBot.cs | 0 .../Bots/TeamsBot.cs | 0 .../Controllers/BotController.cs | 0 .../Dialogs/LogoutDialog.cs | 0 .../Dialogs/MainDialog.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Installed.png | Bin .../Images/3.Logged_In.png | Bin .../Images/4.Your_Token.png | Bin .../Images/BotConversationSsoQuickStart.gif | Bin .../Program.cs | 0 .../bot-conversation-sso-quickstart/README.md | 0 .../SimpleGraphClient.cs | 0 .../TeamsSSOAdapter.cs | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../Bots/ActivityBot.cs | 0 .../Cards/PersonalScopeCard.json | 0 .../Cards/TeamsScopeCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/PepolePickerAdaptiveCard.gif | Bin .../Images/Welcome.png | Bin .../Images/people-picker-card.png | Bin .../Images/people-picker-id.png | Bin .../Images/people-picker-info.png | Bin .../PeoplePicker.csproj | 0 .../Program.cs | 0 .../bot-people-picker-adaptive-card/README.md | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 .../wwwroot/default.htm | 0 .../Bots/ActivityBot.cs | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Installed.png | Bin .../Images/3.Interaction.png | Bin .../Images/4.1_and_2_Command_Interaction.png | Bin .../Images/5.Install_to_GC.png | Bin .../Images/6.Installed.png | Bin .../Images/7.1_and_2_Command_Interaction.png | Bin .../Bot_Channel_Messenging-RSC-nodejs-gif.gif | Bin .../Program.cs | 0 .../README.md | 0 .../ReceiveMessagesWithRSC.csproj | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 .../BotRequestApproval.csproj | 0 .../bot-request-approval/Bots/ActivityBot.cs | 0 .../Cards/ApprovedCard.json | 0 .../Cards/AssignedToCard.json | 0 .../Cards/CancelCard.json | 0 .../Cards/InitialCard.json | 0 .../Cards/OtherMembersCard.json | 0 .../Cards/RejectedCard.json | 0 .../Cards/RequestCard.json | 0 .../Cards/RequestDetailsCardForUser.json | 0 .../Controllers/BotController.cs | 0 .../Images/ApproveRejectCard.png | Bin .../Images/ApprovedRequest.png | Bin .../Images/CancelledRequest.png | Bin .../Images/CreateTask.png | Bin .../Images/EditCancelCard.png | Bin .../bot-request-approval/Images/EditTask.png | Bin .../Images/InitialCard.png | Bin .../Images/ManagerCard.png | Bin .../Images/OtherMemberCard.png | Bin .../bot-request-approval/Images/Preview.gif | Bin .../Images/RequestCard.png | Bin .../Images/StatusCard.png | Bin .../bot-request-approval/Images/UserCard.png | Bin .../Models/InitialSequentialCard.cs | 0 .../Models/RequestDetails.cs | 0 .../Teams/bot-request-approval/Program.cs | 0 .../Teams/bot-request-approval/README.md | 0 .../appPackage}/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage}/outline.png | Bin .../bot-request-approval/appsettings.json | 0 .../bot-request-approval/assets/sample.json | 0 .../Teams/bot-tag-mention/Bots/DialogBot.cs | 0 .../Bots/TeamsTagMentionBot.cs | 0 .../Controllers/BotController.cs | 0 .../bot-tag-mention/Dialogs/LogoutDialog.cs | 0 .../bot-tag-mention/Dialogs/MainDialog.cs | 0 .../Images/1.AddPersonalScope.png | Bin .../Images/2.LoginWithPersonalScope.png | Bin .../Images/3.AddToTeamsScope.png | Bin .../Images/4.WelcomeMessage_Teams.png | Bin .../Images/5.MetionedTag-2.png | Bin .../bot-tag-mention/Images/5.MetionedTag.png | Bin .../Images/6.TagMentionDetails.png | Bin .../Images/7.MessageWhenNoTagFound.png | Bin .../Images/8.WithOutCommand.png | Bin .../Images/Tag-mention-bot.gif | Bin .../Teams/bot-tag-mention/Program.cs | 0 .../Teams/bot-tag-mention/README.md | 0 .../Resources/UserMentionCardTemplate.json | 0 .../bot-tag-mention/SimpleGraphClient.cs | 0 .../bot-tag-mention/TagMentionBot.csproj | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../bot-tag-mention/appManifest/manifest.json | 0 .../Teams/bot-tag-mention/appsettings.json | 0 .../AppManifest}/color.png | Bin .../AppManifest/manifest.json | 0 .../AppManifest}/outline.png | Bin .../Bots/DialogBot.cs | 0 .../bot-teams-authentication/Bots/TeamsBot.cs | 0 .../Controllers/BotController.cs | 0 .../Dialogs/LogoutDialog.cs | 0 .../Dialogs/MainDialog.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Welcome.png | Bin .../Images/3.AuthSuccess.png | Bin .../Images/4.AuthToken.png | Bin .../Images/5.Logout.png | Bin .../Images/auth-consent.png | Bin .../Images/bot-teams-auth.gif | Bin .../Teams/bot-teams-authentication/Program.cs | 0 .../Teams/bot-teams-authentication/README.md | 0 .../SimpleGraphClient.cs | 0 .../bot-teams-authentication/TeamsAuth.csproj | 0 .../bot-teams-authentication/appsettings.json | 0 .../assets/sample.json | 0 .../Bots/ActivityBot.cs | 0 .../Cards/DependentDropdown.json | 0 .../Cards/DynamicSearchCard.json | 0 .../Cards/StaticSearchCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/10.CountryOptions.png | Bin .../Images/11.CitiesAsPerTheCountry.png | Bin .../Images/12.SelectedDependantDropdown.png | Bin .../Images/2.Welcome.png | Bin .../Images/3.StaticSearch.png | Bin .../Images/4.StaticSearch2.png | Bin .../Images/5.SelectedOption.png | Bin .../Images/6.DynamicSearch.png | Bin .../Images/7.DynamicSearch2.png | Bin .../Images/8.SelectedDynamicSearch.png | Bin .../Images/9.DependantDropdown.png | Bin .../Images/TypedSearchModule.gif | Bin .../Models/InitialSequentialCard.cs | 0 .../Program.cs | 0 .../README.md | 0 .../TypeaheadSearch.csproj | 0 .../appPackage}/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage}/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 .../OAuthPromptTests.cs | 2 +- 473 files changed, 360 insertions(+), 2015 deletions(-) delete mode 100644 src/samples/Application/AuthenticationBot/AuthenticationBot.csproj delete mode 100644 src/samples/Application/AuthenticationBot/BotController.cs delete mode 100644 src/samples/Application/AuthenticationBot/Program.cs delete mode 100644 src/samples/Application/AuthenticationBot/README.md delete mode 100644 src/samples/Application/AuthenticationBot/StateExtensions.cs delete mode 100644 src/samples/Application/AuthenticationBot/appManifest/manifest.json delete mode 100644 src/samples/Application/AuthenticationBot/appsettings.json delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/.gitignore delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Config.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Program.cs delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/README.md delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/manifest.json delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appPackage/outline.png delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/appsettings.json delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/assets/card.png delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/assets/search.png delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml delete mode 100644 src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml delete mode 100644 src/samples/Application/messaging.echoBot/BotController.cs delete mode 100644 src/samples/Application/messaging.echoBot/EchoBot.csproj delete mode 100644 src/samples/Application/messaging.echoBot/EchoBot.sln delete mode 100644 src/samples/Application/messaging.echoBot/Program.cs delete mode 100644 src/samples/Application/messaging.echoBot/README.md delete mode 100644 src/samples/Application/messaging.echoBot/appsettings.json delete mode 100644 src/samples/AuthenticationBot/AuthBot.cs rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/AddTokenHandler.cs (100%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/ChatConsoleService.cs (100%) rename src/samples/{CopilotStudioClientSample/CopilotStudioClientSample.csproj => CopilotStudioClient/CopilotStudioClient/CopilotStudioClient.csproj} (82%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/Program.cs (100%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/Properties/launchSettings.TEMPLATE.json (100%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/README.md (100%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/SampleConnectionSettings.cs (100%) rename src/samples/{CopilotStudioClientSample => CopilotStudioClient/CopilotStudioClient}/appsettings.json (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/AddTokenHandler.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/Data/Evaluation Dataset.csv (99%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalClient.csproj (83%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalClient.sln (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalClientConfig.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalDataset.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalDatasetCsvMap.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvalDatasetResultCsvMap.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/EvaluationService.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/Program.cs (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/README.md (100%) rename src/samples/{ => CopilotStudioClient}/EvalClient/appsettings.json (100%) delete mode 100644 src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs delete mode 100644 src/samples/EchoBot/MyBot.cs rename src/samples/{Application/messaging.echoBot => EchoBot}/StateExtensions.cs (100%) delete mode 100644 src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png delete mode 100644 src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png rename src/samples/{ => ToMigrate}/BotToBot/.gitignore (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/.config/dotnet-tools.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Bot1.csproj (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Bots/Bot1.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Controllers/Bot2ResponseController.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Program.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/Properties/launchSettings.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/README.md (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/appsettings.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot1/wwwroot/default.htm (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/Bot2.csproj (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/BotAdapterWithErrorHandler.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/Bots/Bot2.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/Program.cs (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/Properties/launchSettings.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/README.md (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/appsettings.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/wwwroot/default.htm (100%) rename src/samples/{ => ToMigrate}/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json (100%) rename src/samples/{ => ToMigrate}/BotToBot/README.md (100%) rename src/samples/{ => ToMigrate}/BotToBot/SimpleBotToBot.sln (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Cards/SuggestedActions.json (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/3.Red.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/4.Green.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/5.Blue.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/6.CardActions.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/README.md (100%) rename src/samples/{Application/AuthenticationBot => ToMigrate/Teams/AdaptiveCardActions}/appManifest/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/appManifest/manifest.json (100%) rename src/samples/{Application/AuthenticationBot => ToMigrate/Teams/AdaptiveCardActions}/appManifest/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/AdaptiveCardActions/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/Bots/TeamsConversationBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/ConversationBot.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/appManifest/icon-color.png (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/appManifest/icon-outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/appManifest/manifest.json (100%) rename src/samples/{ => ToMigrate}/Teams/ConversationBot/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/AppManifest/icon-color.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/AppManifest/icon-outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/AppManifest/manifest.json (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/Add-App.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/Link-Unfurling.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/OpenAppIcon.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/OpenNewMail.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/SearchInExtension.png (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/LinkUnfurling.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/LinkUnfurling/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/1.setup.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/2.add_to_meeting.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/3.tab_configuration.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/4.tab_context_details.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/AddToChat.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/Meeting-Details.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/MeetingContext.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/Participant-Details.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/ParticipantContext.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/Tab-View.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/meetingTabContext.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/MeetingContextApp.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/README.md (100%) rename src/samples/{Teams/AdaptiveCardActions/appManifest => ToMigrate/Teams/Meeting-Context-App/appPackage}/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/appPackage/manifest.json (100%) rename src/samples/{Teams/AdaptiveCardActions/appManifest => ToMigrate/Teams/Meeting-Context-App/appPackage}/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meeting-Context-App/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Cards/AgendaCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Cards/QuestionTemplate.json (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/2.Home_Page.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/4.Option_Card.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/5.Output_in_Chat.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/7.Popup_Window.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Images/MeetingNotification.gif (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/ActionBase.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/AgendaItem.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/MeetingAgenda.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/MeetingNotification.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/ParticipantDetail.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/PushAgendaAction.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Models/SubmitFeedback.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Pages/_ViewStart.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/Titles.cs (100%) rename src/samples/{Teams/Meeting-Context-App => ToMigrate/Teams/Meetings-Notification}/appPackage/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/appPackage/manifest.json (100%) rename src/samples/{Teams/Meeting-Context-App => ToMigrate/Teams/Meetings-Notification}/appPackage/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/Meetings-Notification/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Images/msgext-search.gif (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/Resources/connectorCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/appManifest/icon-color.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/appManifest/manifest.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/MessagingExtensionsSearch/wwwroot/default.html (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Helper/AdaptiveCardHelper.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Helper/ApplicationSettings.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Helper/DeeplinkHelper.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Helper/UIConstants.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/1.InstallApp.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/10.GroupChat.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/11.GroupDialogs.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/12.GroupCustomForm.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/13.GroupResults.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/2.Dialogs.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/3.AdaptiveCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/5.CustomForm.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/6.Results.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/7.YouTube.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/8.Task.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/9.PowerApp.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/CardTaskFetchValue.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/TaskModuleIds.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/TaskModuleResponseFactory.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/TaskModuleUIConstants.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Models/UISettings.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/Configure.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/CustomForm.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/CustomForm.cshtml.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/HelloWorld.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/PowerApp.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/Shared/_Layout.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/Tasks.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/YouTube.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Pages/_ViewStart.cshtml (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/Resources/adaptiveCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/TaskModule.csproj (100%) rename src/samples/{Teams/Meetings-Notification/appPackage => ToMigrate/Teams/TaskModule/appManifest}/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/appManifest/manifest.json (100%) rename src/samples/{Teams/Meetings-Notification/appPackage => ToMigrate/Teams/TaskModule/appManifest}/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/assets/sample.json (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/wwwroot/css/Site.css (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/wwwroot/css/custom.css (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/wwwroot/css/msteams-16.css (100%) rename src/samples/{ => ToMigrate}/Teams/TaskModule/wwwroot/default.htm (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/BotAllCards.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Bots/DialogBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Bots/TeamsBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Cards/AllCards.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Dialogs/MainDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/10.CollectionCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/11.ConnectorCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/2.Welcome.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/3.SelectCards.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/4.AdaptiveCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/5.HeroCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/6.OathCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/7.SignInCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/8.ThumbnailCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/9.ListCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/AdaptiveCardMedia.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/Authentication.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/OauthConnection.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Images/allBotCardsGif.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/adaptiveCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/adaptiveCardMedia.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/collectionsCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/heroCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/listCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/o365ConnectorCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/Resources/thumbnailCard.json (100%) rename src/samples/{Teams/TaskModule => ToMigrate/Teams/bot-all-cards}/appManifest/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/appManifest/manifest.json (100%) rename src/samples/{Teams/TaskModule => ToMigrate/Teams/bot-all-cards}/appManifest/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-all-cards/wwwroot/default.htm (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs (100%) rename src/samples/{Teams/bot-all-cards => ToMigrate/Teams/bot-conversation-sso-quickstart}/appManifest/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json (100%) rename src/samples/{Teams/bot-all-cards => ToMigrate/Teams/bot-conversation-sso-quickstart}/appManifest/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-conversation-sso-quickstart/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Images/Welcome.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/README.md (100%) rename src/samples/{Teams/bot-conversation-sso-quickstart => ToMigrate/Teams/bot-people-picker-adaptive-card}/appManifest/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json (100%) rename src/samples/{Teams/bot-conversation-sso-quickstart => ToMigrate/Teams/bot-people-picker-adaptive-card}/appManifest/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/assets/sample.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj (100%) rename src/samples/{Teams/bot-people-picker-adaptive-card => ToMigrate/Teams/bot-receive-channel-messages-withRSC}/appManifest/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json (100%) rename src/samples/{Teams/bot-people-picker-adaptive-card => ToMigrate/Teams/bot-receive-channel-messages-withRSC}/appManifest/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-receive-channel-messages-withRSC/assets/sample.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/BotRequestApproval.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Bots/ActivityBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/ApprovedCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/AssignedToCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/CancelCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/InitialCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/OtherMembersCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/RejectedCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/RequestCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/ApproveRejectCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/ApprovedRequest.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/CancelledRequest.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/CreateTask.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/EditCancelCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/EditTask.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/InitialCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/ManagerCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/OtherMemberCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/Preview.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/RequestCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/StatusCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Images/UserCard.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Models/InitialSequentialCard.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Models/RequestDetails.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/README.md (100%) rename src/samples/{Teams/bot-receive-channel-messages-withRSC/appManifest => ToMigrate/Teams/bot-request-approval/appPackage}/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/appPackage/manifest.json (100%) rename src/samples/{Teams/bot-receive-channel-messages-withRSC/appManifest => ToMigrate/Teams/bot-request-approval/appPackage}/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-request-approval/assets/sample.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Bots/DialogBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Dialogs/MainDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/1.AddPersonalScope.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/5.MetionedTag-2.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/5.MetionedTag.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/6.TagMentionDetails.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/8.WithOutCommand.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Images/Tag-mention-bot.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/SimpleGraphClient.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/TagMentionBot.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/appManifest/icon-color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/appManifest/icon-outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/appManifest/manifest.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-tag-mention/appsettings.json (100%) rename src/samples/{Teams/bot-request-approval/appPackage => ToMigrate/Teams/bot-teams-authentication/AppManifest}/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/AppManifest/manifest.json (100%) rename src/samples/{Teams/bot-request-approval/appPackage => ToMigrate/Teams/bot-teams-authentication/AppManifest}/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Bots/DialogBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Bots/TeamsBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Dialogs/MainDialog.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/2.Welcome.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/3.AuthSuccess.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/4.AuthToken.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/5.Logout.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/auth-consent.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Images/bot-teams-auth.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/SimpleGraphClient.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/TeamsAuth.csproj (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-teams-authentication/assets/sample.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/Program.cs (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/README.md (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj (100%) rename src/samples/{Teams/bot-teams-authentication/AppManifest => ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage}/color.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json (100%) rename src/samples/{Teams/bot-teams-authentication/AppManifest => ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage}/outline.png (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json (100%) rename src/samples/{ => ToMigrate}/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json (100%) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index be305884..e11818d9 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{5C0A039F-B639-40D7-BC3D-759DA8AEAA66}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Teams", "Teams", "{183D0E91-B84E-46D7-B653-6D85B4CCF804}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AD743B78-D61F-4FBF-B620-FA83CE599A50}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{674A812C-7287-4883-97F9-697D83750648}" @@ -27,15 +25,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoBot", "samples\EchoBot\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Client", "libraries\Client\Microsoft.Agents.Client\Microsoft.Agents.Client.csproj", "{9712326C-F202-4A8C-9F16-72D20CB7F353}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BotToBot", "BotToBot", "{297B5F2B-47E4-4C50-906C-32D124E9C306}" - ProjectSection(SolutionItems) = preProject - samples\BotToBot\README.md = samples\BotToBot\README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot1", "samples\BotToBot\Bot1\Bot1.csproj", "{89F1C954-98F1-480E-9218-7947EA446024}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot2", "samples\BotToBot\Bot2\Bot2.csproj", "{5BC745C9-7366-428B-A0B5-B20557B1EBA8}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder.Testing", "tests\BotBuilder.Testing\Microsoft.Agents.BotBuilder.Testing.csproj", "{D08C3B96-BDFE-489E-BD46-E5B2B885BE07}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder.Dialogs.Tests", "tests\Microsoft.Agents.BotBuilder.Dialogs.Tests\Microsoft.Agents.BotBuilder.Dialogs.Tests.csproj", "{9AD702C6-E1E1-4328-A249-09702853DE8E}" @@ -54,34 +43,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Auth.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthenticationBot", "samples\AuthenticationBot\AuthenticationBot.csproj", "{CBC8720C-5169-4034-B52B-8FAAA43687B9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCardActions", "samples\Teams\AdaptiveCardActions\AdaptiveCardActions.csproj", "{A94520CC-5B8D-44A0-ADDD-88CAC46DDF78}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.CopilotStudio.Client", "libraries\Client\Microsoft.Agents.CopilotStudio.Client\Microsoft.Agents.CopilotStudio.Client.csproj", "{2CEAC466-C0BD-4CD8-ACA3-4282421A869D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CopilotStudioClientSample", "samples\CopilotStudioClientSample\CopilotStudioClientSample.csproj", "{25922F8C-0AE7-4AB8-8869-A3E3B332A80B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.CopilotStudio.Client.Tests", "tests\Microsoft.Agents.CopilotStudio.Client.Tests\Microsoft.Agents.CopilotStudio.Client.Tests.csproj", "{85EFACDD-33EA-4C01-B25D-1580C2059B20}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConversationBot", "samples\Teams\ConversationBot\ConversationBot.csproj", "{BC50F57F-4CE7-4015-9644-B539D89C0C67}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaskModule", "samples\Teams\TaskModule\TaskModule.csproj", "{98AE4D29-62C9-4302-A1D9-6882FF8C735A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingExtensionsSearch", "samples\Teams\MessagingExtensionsSearch\MessagingExtensionsSearch.csproj", "{5088E98A-92E1-44FA-8B5B-3BFEFB12F162}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.Client.Tests", "tests\Microsoft.Agents.Client.Tests\Microsoft.Agents.Client.Tests.csproj", "{C3C67596-AF6A-486C-B8C3-76DCAE70AB72}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinkUnfurling", "samples\Teams\LinkUnfurling\LinkUnfurling.csproj", "{2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsAuth", "samples\Teams\bot-teams-authentication\TeamsAuth.csproj", "{A059A557-AC73-4CC0-8909-E458BEF05AB5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAllCards", "samples\Teams\bot-all-cards\BotAllCards.csproj", "{5AEA2118-784B-4177-B37A-4C748D0BA176}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PeoplePicker", "samples\Teams\bot-people-picker-adaptive-card\PeoplePicker.csproj", "{CD17A4F9-B07C-4888-89FB-7A765E67FA48}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReceiveMessagesWithRSC", "samples\Teams\bot-receive-channel-messages-withRSC\ReceiveMessagesWithRSC.csproj", "{362374EB-3641-436A-8059-6A96643E484D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotConversationSsoQuickstart", "samples\Teams\bot-conversation-sso-quickstart\BotConversationSsoQuickstart.csproj", "{88073EDB-D99C-48FD-B586-DB2ACA4CC552}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CopilotStudioEchoSkill", "samples\CopilotStudioEchoSkill\CopilotStudioEchoSkill.csproj", "{FAC68927-EC35-464F-BEE0-824B04D39B62}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SemanticKernel", "SemanticKernel", "{5059670B-CB2F-4D97-8B24-872FD1EC9D99}" @@ -118,42 +85,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.BotBuilder EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Agents.State.Tests", "tests\Microsoft.Agents.State.Tests\Microsoft.Agents.State.Tests.csproj", "{71813B2D-D6A8-4388-9541-9B1287C93F19}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvalClient", "samples\EvalClient\EvalClient.csproj", "{A839D635-0382-4E4C-8052-1F18B71434EE}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Hosting.AspNetCore.Tests", "tests\Microsoft.Agents.Hosting.AspNetCore\Microsoft.Agents.Hosting.AspNetCore.Tests.csproj", "{7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InMeetingNotificationsBot", "samples\Teams\Meetings-Notification\InMeetingNotificationsBot.csproj", "{06E490F7-F0BB-E3C4-54FE-5210627292A1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagMentionBot", "samples\Teams\bot-tag-mention\TagMentionBot.csproj", "{BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotRequestApproval", "samples\Teams\bot-request-approval\BotRequestApproval.csproj", "{BF587311-1240-889C-E6AE-ED61A6ED2B37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeaheadSearch", "samples\Teams\bot-type-ahead-search-adaptive-cards\TypeaheadSearch.csproj", "{697C1093-D392-8ABC-2BC8-F955022B1853}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeetingContextApp", "samples\Teams\Meeting-Context-App\MeetingContextApp.csproj", "{153EA430-9914-18E7-409F-7292CB1914AB}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Authentication.Msal.Tests", "tests\Microsoft.Agents.Authentication.Msal.Tests\Microsoft.Agents.Authentication.Msal.Tests.csproj", "{B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.TestSupport", "tests\Microsoft.Agents.TestSupport\Microsoft.Agents.TestSupport.csproj", "{8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{851DB8B0-CD62-414F-B370-EC3680563B6E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "samples\Application\messaging.echoBot\EchoBot.csproj", "{47E36E7C-7160-4ED3-A43A-D53D27361CD4}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{927E4F54-6FBC-4390-BF64-BF3C1874C1AB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.SharePoint", "libraries\Extensions\Microsoft.Agents.Extensions.SharePoint\Microsoft.Agents.Extensions.SharePoint.csproj", "{A8EDAEA3-4057-4862-BC92-F9CFA645246A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Agents.Extensions.Teams", "libraries\Extensions\Microsoft.Agents.Extensions.Teams\Microsoft.Agents.Extensions.Teams.csproj", "{348A61E5-E16A-47ED-B621-F2C61E1E316D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchCommand", "samples\Application\messageExtensions.a.searchCommand\SearchCommand.csproj", "{AC149C90-3191-4995-B6F5-7EA35F311EAB}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{E08758F4-9774-472A-B0E0-CA057F6296AA}" ProjectSection(SolutionItems) = preProject samples\Shared\AspNetExtensions.cs = samples\Shared\AspNetExtensions.cs EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBot", "samples\Application\AuthenticationBot\AuthenticationBot.csproj", "{0ACB6011-D08C-43D5-B4BA-13E270DF24AE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CopilotStudioClient", "CopilotStudioClient", "{295CD61D-DB20-4DF5-A917-2665DB79A6E4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compat", "Compat", "{36494671-1A2D-47F9-B53D-354E0690DA82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopilotStudioClient", "samples\CopilotStudioClient\CopilotStudioClient\CopilotStudioClient.csproj", "{CA58CF4C-2D7E-49CE-974C-9939321B1612}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvalClient", "samples\CopilotStudioClient\EvalClient\EvalClient.csproj", "{A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -173,14 +128,6 @@ Global {9712326C-F202-4A8C-9F16-72D20CB7F353}.Debug|Any CPU.Build.0 = Debug|Any CPU {9712326C-F202-4A8C-9F16-72D20CB7F353}.Release|Any CPU.ActiveCfg = Release|Any CPU {9712326C-F202-4A8C-9F16-72D20CB7F353}.Release|Any CPU.Build.0 = Release|Any CPU - {89F1C954-98F1-480E-9218-7947EA446024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89F1C954-98F1-480E-9218-7947EA446024}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89F1C954-98F1-480E-9218-7947EA446024}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89F1C954-98F1-480E-9218-7947EA446024}.Release|Any CPU.Build.0 = Release|Any CPU - {5BC745C9-7366-428B-A0B5-B20557B1EBA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BC745C9-7366-428B-A0B5-B20557B1EBA8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BC745C9-7366-428B-A0B5-B20557B1EBA8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BC745C9-7366-428B-A0B5-B20557B1EBA8}.Release|Any CPU.Build.0 = Release|Any CPU {D08C3B96-BDFE-489E-BD46-E5B2B885BE07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D08C3B96-BDFE-489E-BD46-E5B2B885BE07}.Debug|Any CPU.Build.0 = Debug|Any CPU {D08C3B96-BDFE-489E-BD46-E5B2B885BE07}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -217,62 +164,18 @@ Global {CBC8720C-5169-4034-B52B-8FAAA43687B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBC8720C-5169-4034-B52B-8FAAA43687B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBC8720C-5169-4034-B52B-8FAAA43687B9}.Release|Any CPU.Build.0 = Release|Any CPU - {A94520CC-5B8D-44A0-ADDD-88CAC46DDF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A94520CC-5B8D-44A0-ADDD-88CAC46DDF78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A94520CC-5B8D-44A0-ADDD-88CAC46DDF78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A94520CC-5B8D-44A0-ADDD-88CAC46DDF78}.Release|Any CPU.Build.0 = Release|Any CPU {2CEAC466-C0BD-4CD8-ACA3-4282421A869D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2CEAC466-C0BD-4CD8-ACA3-4282421A869D}.Debug|Any CPU.Build.0 = Debug|Any CPU {2CEAC466-C0BD-4CD8-ACA3-4282421A869D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2CEAC466-C0BD-4CD8-ACA3-4282421A869D}.Release|Any CPU.Build.0 = Release|Any CPU - {25922F8C-0AE7-4AB8-8869-A3E3B332A80B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25922F8C-0AE7-4AB8-8869-A3E3B332A80B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25922F8C-0AE7-4AB8-8869-A3E3B332A80B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25922F8C-0AE7-4AB8-8869-A3E3B332A80B}.Release|Any CPU.Build.0 = Release|Any CPU {85EFACDD-33EA-4C01-B25D-1580C2059B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {85EFACDD-33EA-4C01-B25D-1580C2059B20}.Debug|Any CPU.Build.0 = Debug|Any CPU {85EFACDD-33EA-4C01-B25D-1580C2059B20}.Release|Any CPU.ActiveCfg = Release|Any CPU {85EFACDD-33EA-4C01-B25D-1580C2059B20}.Release|Any CPU.Build.0 = Release|Any CPU - {BC50F57F-4CE7-4015-9644-B539D89C0C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC50F57F-4CE7-4015-9644-B539D89C0C67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC50F57F-4CE7-4015-9644-B539D89C0C67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC50F57F-4CE7-4015-9644-B539D89C0C67}.Release|Any CPU.Build.0 = Release|Any CPU - {98AE4D29-62C9-4302-A1D9-6882FF8C735A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98AE4D29-62C9-4302-A1D9-6882FF8C735A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98AE4D29-62C9-4302-A1D9-6882FF8C735A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98AE4D29-62C9-4302-A1D9-6882FF8C735A}.Release|Any CPU.Build.0 = Release|Any CPU - {5088E98A-92E1-44FA-8B5B-3BFEFB12F162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5088E98A-92E1-44FA-8B5B-3BFEFB12F162}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5088E98A-92E1-44FA-8B5B-3BFEFB12F162}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5088E98A-92E1-44FA-8B5B-3BFEFB12F162}.Release|Any CPU.Build.0 = Release|Any CPU {C3C67596-AF6A-486C-B8C3-76DCAE70AB72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3C67596-AF6A-486C-B8C3-76DCAE70AB72}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3C67596-AF6A-486C-B8C3-76DCAE70AB72}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3C67596-AF6A-486C-B8C3-76DCAE70AB72}.Release|Any CPU.Build.0 = Release|Any CPU - {2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711}.Release|Any CPU.Build.0 = Release|Any CPU - {A059A557-AC73-4CC0-8909-E458BEF05AB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A059A557-AC73-4CC0-8909-E458BEF05AB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A059A557-AC73-4CC0-8909-E458BEF05AB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A059A557-AC73-4CC0-8909-E458BEF05AB5}.Release|Any CPU.Build.0 = Release|Any CPU - {5AEA2118-784B-4177-B37A-4C748D0BA176}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5AEA2118-784B-4177-B37A-4C748D0BA176}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AEA2118-784B-4177-B37A-4C748D0BA176}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5AEA2118-784B-4177-B37A-4C748D0BA176}.Release|Any CPU.Build.0 = Release|Any CPU - {CD17A4F9-B07C-4888-89FB-7A765E67FA48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD17A4F9-B07C-4888-89FB-7A765E67FA48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD17A4F9-B07C-4888-89FB-7A765E67FA48}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD17A4F9-B07C-4888-89FB-7A765E67FA48}.Release|Any CPU.Build.0 = Release|Any CPU - {362374EB-3641-436A-8059-6A96643E484D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {362374EB-3641-436A-8059-6A96643E484D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {362374EB-3641-436A-8059-6A96643E484D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {362374EB-3641-436A-8059-6A96643E484D}.Release|Any CPU.Build.0 = Release|Any CPU - {88073EDB-D99C-48FD-B586-DB2ACA4CC552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88073EDB-D99C-48FD-B586-DB2ACA4CC552}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88073EDB-D99C-48FD-B586-DB2ACA4CC552}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88073EDB-D99C-48FD-B586-DB2ACA4CC552}.Release|Any CPU.Build.0 = Release|Any CPU {FAC68927-EC35-464F-BEE0-824B04D39B62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAC68927-EC35-464F-BEE0-824B04D39B62}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAC68927-EC35-464F-BEE0-824B04D39B62}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -337,34 +240,10 @@ Global {71813B2D-D6A8-4388-9541-9B1287C93F19}.Debug|Any CPU.Build.0 = Debug|Any CPU {71813B2D-D6A8-4388-9541-9B1287C93F19}.Release|Any CPU.ActiveCfg = Release|Any CPU {71813B2D-D6A8-4388-9541-9B1287C93F19}.Release|Any CPU.Build.0 = Release|Any CPU - {A839D635-0382-4E4C-8052-1F18B71434EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A839D635-0382-4E4C-8052-1F18B71434EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A839D635-0382-4E4C-8052-1F18B71434EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A839D635-0382-4E4C-8052-1F18B71434EE}.Release|Any CPU.Build.0 = Release|Any CPU {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D}.Release|Any CPU.Build.0 = Release|Any CPU - {06E490F7-F0BB-E3C4-54FE-5210627292A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06E490F7-F0BB-E3C4-54FE-5210627292A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06E490F7-F0BB-E3C4-54FE-5210627292A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06E490F7-F0BB-E3C4-54FE-5210627292A1}.Release|Any CPU.Build.0 = Release|Any CPU - {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8}.Release|Any CPU.Build.0 = Release|Any CPU - {BF587311-1240-889C-E6AE-ED61A6ED2B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF587311-1240-889C-E6AE-ED61A6ED2B37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF587311-1240-889C-E6AE-ED61A6ED2B37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF587311-1240-889C-E6AE-ED61A6ED2B37}.Release|Any CPU.Build.0 = Release|Any CPU - {697C1093-D392-8ABC-2BC8-F955022B1853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {697C1093-D392-8ABC-2BC8-F955022B1853}.Debug|Any CPU.Build.0 = Debug|Any CPU - {697C1093-D392-8ABC-2BC8-F955022B1853}.Release|Any CPU.ActiveCfg = Release|Any CPU - {697C1093-D392-8ABC-2BC8-F955022B1853}.Release|Any CPU.Build.0 = Release|Any CPU - {153EA430-9914-18E7-409F-7292CB1914AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {153EA430-9914-18E7-409F-7292CB1914AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {153EA430-9914-18E7-409F-7292CB1914AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {153EA430-9914-18E7-409F-7292CB1914AB}.Release|Any CPU.Build.0 = Release|Any CPU {B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -373,10 +252,6 @@ Global {8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC}.Release|Any CPU.Build.0 = Release|Any CPU - {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47E36E7C-7160-4ED3-A43A-D53D27361CD4}.Release|Any CPU.Build.0 = Release|Any CPU {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Debug|Any CPU.Build.0 = Debug|Any CPU {A8EDAEA3-4057-4862-BC92-F9CFA645246A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -385,23 +260,20 @@ Global {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Debug|Any CPU.Build.0 = Debug|Any CPU {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.ActiveCfg = Release|Any CPU {348A61E5-E16A-47ED-B621-F2C61E1E316D}.Release|Any CPU.Build.0 = Release|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Build.0 = Release|Any CPU - {AC149C90-3191-4995-B6F5-7EA35F311EAB}.Release|Any CPU.Deploy.0 = Release|Any CPU - {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0ACB6011-D08C-43D5-B4BA-13E270DF24AE}.Release|Any CPU.Build.0 = Release|Any CPU + {CA58CF4C-2D7E-49CE-974C-9939321B1612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA58CF4C-2D7E-49CE-974C-9939321B1612}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA58CF4C-2D7E-49CE-974C-9939321B1612}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA58CF4C-2D7E-49CE-974C-9939321B1612}.Release|Any CPU.Build.0 = Release|Any CPU + {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {5C0A039F-B639-40D7-BC3D-759DA8AEAA66} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} - {183D0E91-B84E-46D7-B653-6D85B4CCF804} = {674A812C-7287-4883-97F9-697D83750648} {C0F4149C-BB92-423D-A8E2-855448E3A0C0} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {9AEE51D5-B0A6-4698-8EAC-ED2E9795225A} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {7A18F0C9-F8AF-4168-B954-6563BB2C1A90} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} @@ -409,9 +281,6 @@ Global {CCEEC142-70BF-42EE-A5DD-54FF80ED5F82} = {C0F4149C-BB92-423D-A8E2-855448E3A0C0} {E0202555-C388-4DE7-81DA-BFAB29784619} = {674A812C-7287-4883-97F9-697D83750648} {9712326C-F202-4A8C-9F16-72D20CB7F353} = {9AEE51D5-B0A6-4698-8EAC-ED2E9795225A} - {297B5F2B-47E4-4C50-906C-32D124E9C306} = {674A812C-7287-4883-97F9-697D83750648} - {89F1C954-98F1-480E-9218-7947EA446024} = {297B5F2B-47E4-4C50-906C-32D124E9C306} - {5BC745C9-7366-428B-A0B5-B20557B1EBA8} = {297B5F2B-47E4-4C50-906C-32D124E9C306} {D08C3B96-BDFE-489E-BD46-E5B2B885BE07} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {9AD702C6-E1E1-4328-A249-09702853DE8E} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {61084CFD-CB22-44DA-A42B-4BAB0F18DDE7} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} @@ -421,20 +290,9 @@ Global {AC905963-D232-4679-888B-22A3D6D39ACB} = {5C0A039F-B639-40D7-BC3D-759DA8AEAA66} {08F7E4C2-2492-4002-9CDF-875C4765AB59} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {CBC8720C-5169-4034-B52B-8FAAA43687B9} = {674A812C-7287-4883-97F9-697D83750648} - {A94520CC-5B8D-44A0-ADDD-88CAC46DDF78} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {2CEAC466-C0BD-4CD8-ACA3-4282421A869D} = {9AEE51D5-B0A6-4698-8EAC-ED2E9795225A} - {25922F8C-0AE7-4AB8-8869-A3E3B332A80B} = {674A812C-7287-4883-97F9-697D83750648} {85EFACDD-33EA-4C01-B25D-1580C2059B20} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {BC50F57F-4CE7-4015-9644-B539D89C0C67} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {98AE4D29-62C9-4302-A1D9-6882FF8C735A} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {5088E98A-92E1-44FA-8B5B-3BFEFB12F162} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {C3C67596-AF6A-486C-B8C3-76DCAE70AB72} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {2D9F4AAB-B0F7-4E07-9690-BB38AF9BB711} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {A059A557-AC73-4CC0-8909-E458BEF05AB5} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {5AEA2118-784B-4177-B37A-4C748D0BA176} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {CD17A4F9-B07C-4888-89FB-7A765E67FA48} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {362374EB-3641-436A-8059-6A96643E484D} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {88073EDB-D99C-48FD-B586-DB2ACA4CC552} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {FAC68927-EC35-464F-BEE0-824B04D39B62} = {674A812C-7287-4883-97F9-697D83750648} {5059670B-CB2F-4D97-8B24-872FD1EC9D99} = {674A812C-7287-4883-97F9-697D83750648} {1889C7A6-B006-4F47-8578-73D95236E94C} = {5059670B-CB2F-4D97-8B24-872FD1EC9D99} @@ -453,23 +311,17 @@ Global {23065AEC-4875-4CDC-A7E5-EE389C201534} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {401F5548-D6EF-4497-B786-D2A6BA8CBF76} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {71813B2D-D6A8-4388-9541-9B1287C93F19} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {A839D635-0382-4E4C-8052-1F18B71434EE} = {674A812C-7287-4883-97F9-697D83750648} {7D1A1CE5-6D9B-4D31-AC77-C3B1787F575D} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {06E490F7-F0BB-E3C4-54FE-5210627292A1} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {BC5EFA6C-7EB5-4803-B7C5-093892E9DBB8} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {BF587311-1240-889C-E6AE-ED61A6ED2B37} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {697C1093-D392-8ABC-2BC8-F955022B1853} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} - {153EA430-9914-18E7-409F-7292CB1914AB} = {183D0E91-B84E-46D7-B653-6D85B4CCF804} {B9AD64EF-EA22-4CAC-B89B-03CEE46CFF4F} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {8DF6799A-AB3E-49A9-9A2A-8BBED20AB0BC} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {851DB8B0-CD62-414F-B370-EC3680563B6E} = {674A812C-7287-4883-97F9-697D83750648} - {47E36E7C-7160-4ED3-A43A-D53D27361CD4} = {851DB8B0-CD62-414F-B370-EC3680563B6E} {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {A8EDAEA3-4057-4862-BC92-F9CFA645246A} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} {348A61E5-E16A-47ED-B621-F2C61E1E316D} = {927E4F54-6FBC-4390-BF64-BF3C1874C1AB} - {AC149C90-3191-4995-B6F5-7EA35F311EAB} = {851DB8B0-CD62-414F-B370-EC3680563B6E} {E08758F4-9774-472A-B0E0-CA057F6296AA} = {674A812C-7287-4883-97F9-697D83750648} - {0ACB6011-D08C-43D5-B4BA-13E270DF24AE} = {851DB8B0-CD62-414F-B370-EC3680563B6E} + {295CD61D-DB20-4DF5-A917-2665DB79A6E4} = {674A812C-7287-4883-97F9-697D83750648} + {36494671-1A2D-47F9-B53D-354E0690DA82} = {674A812C-7287-4883-97F9-697D83750648} + {CA58CF4C-2D7E-49CE-974C-9939321B1612} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} + {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index 94ccd849..c568ec69 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -148,7 +148,7 @@ public override async Task BeginDialogAsync(DialogContext dc, state[PersistedExpires] = DateTime.UtcNow.AddMilliseconds(timeout); SetCallerInfoInDialogState(state, dc.Context); - var token = await _dialogOAuthFlow.BeginFlowAsync(dc.Context, () => Task.FromResult(opt?.Prompt), cancellationToken).ConfigureAwait(false); + var token = await _dialogOAuthFlow.BeginFlowAsync(dc.Context, opt?.Prompt == null ? null : () => Task.FromResult(opt?.Prompt), cancellationToken).ConfigureAwait(false); if (token != null) { // Return token diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs index 7bc89523..617490fe 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs @@ -225,7 +225,7 @@ private void AddManualSignInCompletionHandler() RouteSelectorAsync routeSelector = (context, _) => Task.FromResult ( string.Equals(context.Activity?.Type, ActivityTypes.Event, StringComparison.OrdinalIgnoreCase) - && string.Equals(context.Activity?.Name, "application/vnd.microsoft.SignInComplete") + && string.Equals(context.Activity?.Name, SignInCompletionEventName) ); RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs index bf654efa..0f44193b 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/TokenService/UserTokenClientWrapper.cs @@ -11,28 +11,28 @@ namespace Microsoft.Agents.BotBuilder.UserAuth.TokenService { public class UserTokenClientWrapper { - public static async Task GetSignInResourceAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) + public static Task GetSignInResourceAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) { IUserTokenClient userTokenClient = GetUserTokenClient(context); - return await userTokenClient.GetSignInResourceAsync(connectionName, context.Activity, null, cancellationToken); + return userTokenClient.GetSignInResourceAsync(connectionName, context.Activity, null, cancellationToken); } - public static async Task GetUserTokenAsync(ITurnContext context, string connectionName, string magicCode, CancellationToken cancellationToken) + public static Task GetUserTokenAsync(ITurnContext context, string connectionName, string magicCode, CancellationToken cancellationToken) { IUserTokenClient userTokenClient = GetUserTokenClient(context); - return await userTokenClient.GetUserTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, magicCode, cancellationToken); + return userTokenClient.GetUserTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, magicCode, cancellationToken); } - public static async Task ExchangeTokenAsync(ITurnContext context, string connectionName, TokenExchangeRequest tokenExchangeRequest, CancellationToken cancellationToken) + public static Task ExchangeTokenAsync(ITurnContext context, string connectionName, TokenExchangeRequest tokenExchangeRequest, CancellationToken cancellationToken) { IUserTokenClient userTokenClient = GetUserTokenClient(context); - return await userTokenClient.ExchangeTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, tokenExchangeRequest, cancellationToken); + return userTokenClient.ExchangeTokenAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, tokenExchangeRequest, cancellationToken); } - public static async Task SignOutUserAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) + public static Task SignOutUserAsync(ITurnContext context, string connectionName, CancellationToken cancellationToken) { IUserTokenClient userTokenClient = GetUserTokenClient(context); - await userTokenClient.SignOutUserAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, cancellationToken); + return userTokenClient.SignOutUserAsync(context.Activity.From.Id, connectionName, context.Activity.ChannelId, cancellationToken); } private static IUserTokenClient GetUserTokenClient(ITurnContext context) diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 9e1661ca..5b5c4346 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -43,7 +43,13 @@ public static void AddCloudAdapter(this IServiceCollection services) where T public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory) { - AddCore(builder); + return AddBot(builder, implementationFactory); + } + + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory) + where TAdapter : CloudAdapter + { + AddCore(builder); builder.Services.AddTransient(implementationFactory); diff --git a/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj b/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj deleted file mode 100644 index a3033d63..00000000 --- a/src/samples/Application/AuthenticationBot/AuthenticationBot.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net8.0 - latest - - - - - - - - - - Always - - - diff --git a/src/samples/Application/AuthenticationBot/BotController.cs b/src/samples/Application/AuthenticationBot/BotController.cs deleted file mode 100644 index 9c054590..00000000 --- a/src/samples/Application/AuthenticationBot/BotController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Agents.Hosting.AspNetCore; -using System.Threading; -using Microsoft.Agents.BotBuilder; - -namespace AuthenticationBot -{ - // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. - // When called, the request has already been authorized and credentials and tokens validated. - [Authorize] - [ApiController] - [Route("api/messages")] - public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase - { - [HttpPost] - public Task PostAsync(CancellationToken cancellationToken) - => adapter.ProcessAsync(Request, Response, bot, cancellationToken); - - } -} diff --git a/src/samples/Application/AuthenticationBot/Program.cs b/src/samples/Application/AuthenticationBot/Program.cs deleted file mode 100644 index 9767c114..00000000 --- a/src/samples/Application/AuthenticationBot/Program.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using AuthenticationBot; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.App; -using Microsoft.Agents.BotBuilder.App.UserAuth; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.BotBuilder.UserAuth.TokenService; -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Hosting.AspNetCore; -using Microsoft.Agents.Samples; -using Microsoft.Agents.Storage; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddHttpClient(); - -builder.Logging.AddConsole(); -builder.Logging.AddDebug(); - -// Add AspNet token validation -builder.Services.AddBotAspNetAuthentication(builder.Configuration); - -// Create the bot as a transient. -builder.Services.AddTransient(sp => new TurnState(sp.GetService())); -builder.AddBot((sp) => -{ - var adapter = sp.GetService(); - var storage = sp.GetService(); - - var authOptions = new UserAuthenticationOptions() - { - // Auto-SignIn will use this OAuth flow - Default = "graph", - - AutoSignIn = UserAuthenticationOptions.AutoSignInOn, //UserAuthenticationOptions.AutoSignInOff, - - Handlers = - [ - new OAuthAuthentication( - "graph", - new OAuthSettings() - { - ConnectionName = builder.Configuration["ConnectionName"] - }, - storage)] - }; - - var appOptions = new ApplicationOptions() - { - Adapter = adapter, - StartTypingTimer = true, - UserAuthentication = authOptions, - TurnStateFactory = () => sp.GetService() - }; - - var app = new Application(appOptions); - - app.Authentication.OnUserSignInSuccess(async (turnContext, turnState, flowName, tokenResponse, cancellationToken) => - { - await turnContext.SendActivityAsync($"Successfully logged in to '{flowName}'", cancellationToken: cancellationToken); - await turnContext.SendActivityAsync($"Token string length: {tokenResponse.Token.Length}", cancellationToken: cancellationToken); - }); - - app.Authentication.OnUserSignInFailure(async (turnContext, turnState, flowName, response, cancellationToken) => - { - // Failed to login - await turnContext.SendActivityAsync($"Failed to login to '{flowName}'", cancellationToken: cancellationToken); - await turnContext.SendActivityAsync($"Error message: {response.Error.Message}", cancellationToken: cancellationToken); - }); - - app.OnMessage("/signin", async (turnContext, turnState, cancellationToken) => - { - await app.Authentication.GetTokenOrStartSignInAsync(turnContext, turnState, "graph", cancellationToken); - }); - - // Listen for user to say "/reset" and then delete state - app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnState.User.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); - }); - - // Listen for user to say "/signout" and then delete cached token - app.OnMessage("/signout", async (turnContext, turnState, cancellationToken) => - { - await app.Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); - await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); - }); - - // Display a welcome message - app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Type anything to start sign in."), cancellationToken); - } - } - }); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => - { - int count = turnState.Conversation.IncrementMessageCount(); - - await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); - }); - - return app; -}); - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.MapGet("/", () => "Microsoft Agents SDK Sample"); - app.UseDeveloperExceptionPage(); - app.MapControllers().AllowAnonymous(); -} -else -{ - app.MapControllers(); -} - -app.Run(); - diff --git a/src/samples/Application/AuthenticationBot/README.md b/src/samples/Application/AuthenticationBot/README.md deleted file mode 100644 index 58acf9ca..00000000 --- a/src/samples/Application/AuthenticationBot/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# OAuth Authentication - -This Agent has been created using [Microsoft 365 Agents Framework](https://github.com/microsoft/agents-for-net), it shows how to use authentication in your Agent using OAuth. - -- The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. -- The samples demonstrates performing OAuth without using the Dialogs package. - -- ## Prerequisites - -- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 -- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) - -## Running this sample - -1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot) - - Record the Application ID, the Tenant ID, and the Client Secret for use below - -1. [Add OAuth to your bot](https://aka.ms/AgentsSDK-AddAuth) - -1. Configuring the token connection in the Agent settings - > The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) - - 1. Open the `appsettings.json` file in the root of the sample project. - - 1. Find the section labeled `Connections`, it should appear similar to this: - - ```json - "TokenValidation": { - "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] - }, - - "Connections": { - "BotServiceConnection": { - "Assembly": "Microsoft.Agents.Authentication.Msal", - "Type": "MsalAuth", - "Settings": { - "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. - "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. - "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. - "Scopes": [ - "https://api.botframework.com/.default" - ], - "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. - } - } - ``` - - 1. Set the **ClientId** to the AppId of the bot identity. - 1. Set the **ClientSecret** to the Secret that was created for your identity. - 1. Set the **TenantId** to the Tenant Id where your application is registered. - 1. Set the **Audience** to the AppId of the bot identity. - - > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. - -1. Update `appsettings.json` - - | Property | Value Description | - |----------------------|-----------| - | ConnectionName | Set the configured bot's OAuth connection name. | - -1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: - > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and update it to match your DevTunnel port. If `./Properties/launchSettings.json`not fount Close and re-open the solution.launchSettings.json have been re-created. - - ```bash - devtunnel host -p 3978 --allow-anonymous - ``` - -1. Update your Azure Bot ``Messaging endpoint`` with the tunnel Url: `{tunnel-url}/api/messages` - -1. Run the bot from a terminal or from Visual Studio - -1. Test via "Test in WebChat"" on your Azure Bot in the Azure Portal. - -## Running this Agent in Teams - -1. Manually update the manifest.json - - Edit the `manifest.json` contained in the `/appManifest` folder - - Replace with your AppId (that was created above) *everywhere* you see the place holder string `<>` - - Replace `<>` with your Agent url. For example, the tunnel host name. - - Zip up the contents of the `/appManifest` folder to create a `manifest.zip` -1. Upload the `manifest.zip` to Teams - - Select **Developer Portal** in the Teams left sidebar - - Select **Apps** (top row) - - Select **Import app**, and select the manifest.zip - -1. Select **Preview in Teams** in the upper right corner - -## Interacting with the Agent - -Type anything to sign-in, or `logout` to sign-out. - -## Further reading -To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. - diff --git a/src/samples/Application/AuthenticationBot/StateExtensions.cs b/src/samples/Application/AuthenticationBot/StateExtensions.cs deleted file mode 100644 index 7c0ab2db..00000000 --- a/src/samples/Application/AuthenticationBot/StateExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder.State; - -namespace AuthenticationBot -{ - public static class StateExtensions - { - public static int MessageCount(this ConversationState state) - { - return state.GetValue("countKey"); - } - - public static void MessageCount(this ConversationState state, int value) - { - state.SetValue("countKey", value); - } - - public static int IncrementMessageCount(this ConversationState state) - { - var count = state.GetValue("countKey"); - state.SetValue("countKey", ++count); - return count; - } - } -} diff --git a/src/samples/Application/AuthenticationBot/appManifest/manifest.json b/src/samples/Application/AuthenticationBot/appManifest/manifest.json deleted file mode 100644 index 66882f98..00000000 --- a/src/samples/Application/AuthenticationBot/appManifest/manifest.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", - "manifestVersion": "1.16", - "version": "1.0.0", - "id": "${{AAD_APP_CLIENT_ID}}", - "packageName": "com.microsoft.agents.oauth", - "developer": { - "name": "Microsoft, Inc.", - "websiteUrl": "https://example.azurewebsites.net", - "privacyUrl": "https://example.azurewebsites.net/privacy", - "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" - }, - "icons": { - "color": "color.png", - "outline": "outline.png" - }, - "name": { - "short": "OAuth Authentication", - "full": "OAuth Authentication" - }, - "description": { - "short": "Sample demonstrating Azure Bot Services user authentication with using a Agent.", - "full": "This sample demonstrates how to integrate Azure AD authentication in an Agent with Single Sign-On (SSO) capabilities built with the Agents Framework" - }, - "accentColor": "#FFFFFF", - "bots": [ - { - "botId": "${{AAD_APP_CLIENT_ID}}", - "scopes": [ - "personal" - ], - "supportsFiles": false, - "isNotificationOnly": false - } - ], - "permissions": [ - "identity", - "messageTeamMembers" - ], - "validDomains": [ - "token.botframework.com", - "<>" - ], - "webApplicationInfo": { - "id": "${{AAD_APP_CLIENT_ID}}", - "resource": "api://botid-${{AAD_APP_CLIENT_ID}}" - } -} \ No newline at end of file diff --git a/src/samples/Application/AuthenticationBot/appsettings.json b/src/samples/Application/AuthenticationBot/appsettings.json deleted file mode 100644 index a6bcf7de..00000000 --- a/src/samples/Application/AuthenticationBot/appsettings.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "ConnectionName": "{{ConnectionName}}", - - "TokenValidation": { - "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] - }, - - "Connections": { - "BotServiceConnection": { - "Settings": { - "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. - "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. - "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. - "Scopes": [ - "https://api.botframework.com/.default" - ] - } - } - }, - "ConnectionsMap": [ - { - "ServiceUrl": "*", - "Connection": "BotServiceConnection" - } - ], - - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft.Copilot": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/.gitignore b/src/samples/Application/messageExtensions.a.searchCommand/.gitignore deleted file mode 100644 index ee83f445..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# VS -.vs - -# User-specific files -*.user - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Teams Toolkit -env/.env.*.user -appPackage/build -.deployment -# Teams Toolkit Local Development -appsettings.Development.json -env/.env.local diff --git a/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs b/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs deleted file mode 100644 index f1ba0b3b..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/ActivityHandlers.cs +++ /dev/null @@ -1,118 +0,0 @@ -using AdaptiveCards; -using AdaptiveCards.Templating; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.App.AdaptiveCards; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Core.Serialization; -using Microsoft.Agents.Extensions.Teams.App.MessageExtensions; -using Microsoft.Agents.Extensions.Teams.Models; -using SearchCommand.Model; -using System.Collections.Specialized; -using System.Web; - -namespace SearchCommand -{ - /// - /// Defines the activity handlers. - /// - public class ActivityHandlers - { - private readonly HttpClient _httpClient; - private readonly string _packageCardFilePath = Path.Combine(".", "Resources", "PackageCard.json"); - - public ActivityHandlers(IHttpClientFactory httpClientFactory) - { - _httpClient = httpClientFactory.CreateClient("WebClient"); - } - - /// - /// Handles Message Extension query events. - /// - public QueryHandlerAsync QueryHandler => async (ITurnContext turnContext, ITurnState turnState, Query> query, CancellationToken cancellationToken) => - { - string text = ProtocolJsonSerializer.ToObject(query.Parameters["queryText"]); - int count = query.Count; - - Package[] packages = await SearchPackages(text, count, cancellationToken); - - // Format search results - List attachments = packages.Select(package => new MessagingExtensionAttachment - { - ContentType = HeroCard.ContentType, - Content = new HeroCard - { - Title = package.Id, - Text = package.Description - }, - Preview = new HeroCard - { - Title = package.Id, - Text = package.Description, - Tap = new CardAction - { - Type = "invoke", - Value = package - } - }.ToAttachment() - }).ToList(); - - return new MessagingExtensionResult - { - Type = "result", - AttachmentLayout = "list", - Attachments = attachments - }; - }; - - /// - /// Handles Message Extension selecting item events. - /// - public SelectItemHandlerAsync SelectItemHandler => async (ITurnContext turnContext, ITurnState turnState, object item, CancellationToken cancellationToken) => - { - CardPackage package = ProtocolJsonSerializer.ToObject(item); - string cardTemplate = await File.ReadAllTextAsync(_packageCardFilePath, cancellationToken)!; - string cardContent = new AdaptiveCardTemplate(cardTemplate).Expand(package); - MessagingExtensionAttachment attachment = new() - { - ContentType = AdaptiveCard.ContentType, - Content = ProtocolJsonSerializer.ToJson(cardContent) - }; - - return new MessagingExtensionResult - { - Type = "result", - AttachmentLayout = "list", - Attachments = new List { attachment } - }; - }; - - private async Task SearchPackages(string text, int size, CancellationToken cancellationToken) - { - // Call NuGet Search API - NameValueCollection query = HttpUtility.ParseQueryString(string.Empty); - query["q"] = text; - query["take"] = size.ToString(); - string queryString = query.ToString()!; - string responseContent; - try - { - responseContent = await _httpClient.GetStringAsync($"https://azuresearch-usnc.nuget.org/query?{queryString}", cancellationToken); - } - catch (Exception) - { - throw; - } - - if (!string.IsNullOrWhiteSpace(responseContent)) - { - var responseObj = ProtocolJsonSerializer.ToJsonElements(responseContent); - return ProtocolJsonSerializer.ToObject(responseObj["data"]); - } - else - { - return Array.Empty(); - } - } - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Config.cs b/src/samples/Application/messageExtensions.a.searchCommand/Config.cs deleted file mode 100644 index 9ef63c1b..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Config.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SearchCommand -{ - public class ConfigOptions - { - public string? BOT_ID { get; set; } - public string? BOT_PASSWORD { get; set; } - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs b/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs deleted file mode 100644 index a824f960..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Controllers/BotController.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Agents.Hosting.AspNetCore; -using Microsoft.Agents.BotBuilder; - -namespace SearchCommand.Bot -{ - // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. - // When called, the request has already been authorized and credentials and tokens validated. - [Authorize] - [ApiController] - [Route("api/messages")] - public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase - { - [HttpPost] - public Task PostAsync(CancellationToken cancellationToken) - => adapter.ProcessAsync(Request, Response, bot, cancellationToken); - - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs b/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs deleted file mode 100644 index 8bcbd50b..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Model/CardPackage.cs +++ /dev/null @@ -1,43 +0,0 @@ - -namespace SearchCommand.Model -{ - /// - /// The strongly typed NuGet package model for Adaptive Card - /// - public class CardPackage - { - public string? Id { get; set; } - - public string? Version { get; set; } - - public string? Description { get; set; } - - public string? Tags { get; set; } - - public string? Authors { get; set; } - - public string? Owners { get; set; } - - public string? LicenseUrl { get; set; } - - public string? ProjectUrl { get; set; } - - public string? NuGetUrl { get; set; } - - public static CardPackage Create(Package package) - { - return new CardPackage - { - Id = package.Id ?? string.Empty, - Version = package.Version ?? string.Empty, - Description = package.Description ?? string.Empty, - Tags = package.Tags == null ? string.Empty : string.Join(", ", package.Tags), - Authors = package.Authors == null ? string.Empty : string.Join(", ", package.Authors), - Owners = package.Owners == null ? string.Empty : string.Join(", ", package.Owners), - LicenseUrl = package.LicenseUrl ?? string.Empty, - ProjectUrl = package.ProjectUrl ?? string.Empty, - NuGetUrl = $"https://www.nuget.org/packages/{package.Id}" - }; - } - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs b/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs deleted file mode 100644 index d3ac8532..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Model/Package.cs +++ /dev/null @@ -1,29 +0,0 @@ - -namespace SearchCommand.Model -{ - /// - /// The strongly typed NuGet package search result - /// - public class Package - { - public string? Id { get; set; } - - public string? Version { get; set; } - - public string? Description { get; set; } - - public string[]? Tags { get; set; } - - public string[]? Authors { get; set; } - - public string[]? Owners { get; set; } - - public string? IconUrl { get; set; } - - public string? LicenseUrl { get; set; } - - public string? ProjectUrl { get; set; } - - public object[]? PackageTypes { get; set; } - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Program.cs b/src/samples/Application/messageExtensions.a.searchCommand/Program.cs deleted file mode 100644 index 1bd9ef7b..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Program.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Extensions.Teams.App; -using Microsoft.Agents.Hosting.AspNetCore; -using Microsoft.Agents.Samples; -using Microsoft.Agents.Storage; -using SearchCommand; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddHttpClient(); -builder.Logging.AddConsole(); - -// Add AspNet token validation -builder.Services.AddBotAspNetAuthentication(builder.Configuration); - -builder.Services.AddSingleton(); - -// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. -builder.AddBot(sp => -{ - TeamsApplicationOptions applicationOptions = new() - { - TurnStateFactory = () => new TurnState(sp.GetService()!) - }; - - TeamsApplication app = new(applicationOptions); - - ActivityHandlers activityHandlers = sp.GetService()!; - - // Listen for search actions - app.MessageExtensions.OnQuery("searchCmd", activityHandlers.QueryHandler); - // Listen for item tap - app.MessageExtensions.OnSelectItem(activityHandlers.SelectItemHandler); - - return app; -}); - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} - -app.UseStaticFiles(); -app.UseRouting(); -app.MapControllers(); - -app.Run(); diff --git a/src/samples/Application/messageExtensions.a.searchCommand/README.md b/src/samples/Application/messageExtensions.a.searchCommand/README.md deleted file mode 100644 index a82734d3..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/README.md +++ /dev/null @@ -1,31 +0,0 @@ -## Summary - -This sample shows how to incorporate a basic Message Extension app into a Microsoft Teams application using [Bot Framework](https://dev.botframework.com) and the Teams AI SDK. Users can search nuget.org for packages. - -## Set up instructions - -All the samples in the C# .NET SDK can be set up in the same way. You can find the step by step instructions here: - [Setup Instructions](../README.md). - -## Interacting with the Message Extension - -You can interact with this app by selecting its app icon in the chat compose area. This opens a dialog that allows you to search NuGet for a package. Selecting a package will output an Adaptive Card with its description to the chat. - -Here's a sample search result: - -![Sample search](assets/search.png) - -And after selecting it outputs an Adaptive Card. - -![Adaptive Card](assets/card.png) - -## Deploy to Azure - -You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. - -You can find deployment instructions [here](../README.md#deploy-to-azure). - -## Further reading - -- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) -- [How Microsoft Teams bots work](https://learn.microsoft.com/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json b/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json deleted file mode 100644 index a907b097..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/Resources/PackageCard.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.2", - "body": [ - { - "type": "TextBlock", - "size": "Medium", - "weight": "Bolder", - "text": "${id}" - }, - { - "type": "FactSet", - "facts": [ - { - "title": "Version", - "value": "${version}" - }, - { - "title": "Description", - "value": "${description}" - }, - { - "title": "Tags", - "value": "${tags}" - }, - { - "title": "Authors", - "value": "${authors}" - }, - { - "title": "Owners", - "value": "${owners}" - } - ] - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "NuGet", - "url": "${nugetUrl}" - }, - { - "type": "Action.OpenUrl", - "title": "Project", - "url": "${projectUrl}" - }, - { - "type": "Action.OpenUrl", - "title": "License", - "url": "${licenseUrl}" - } - ] -} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj deleted file mode 100644 index 5ba96922..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - diff --git a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln b/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln deleted file mode 100644 index 06946a52..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/SearchCommand.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.33906.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SearchCommand", "SearchCommand.csproj", "{2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Build.0 = Release|Any CPU - {2523B6D1-DFE1-4512-ADB7-C8084E27A0AE}.Release|Any CPU.Deploy.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {436748E9-F64E-4E1D-9BEE-1AE35954FA4F} - EndGlobalSection -EndGlobal diff --git a/src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png b/src/samples/Application/messageExtensions.a.searchCommand/appPackage/color.png deleted file mode 100644 index f27ccf2036bf2264dc0d11edf2af2bda62e4efdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1066 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaloCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49xpIT^vIy7~kGC%#(I!aJU%yVD^#PnvNe5 zY1um`Oj+#WC3y9e;Us63+9EE_EXjNxu|}7}jyc6;|Ee3L%wi^d-gxKYxi>fe9)4Wc zxa_YvcME5O3F8DchD$6Cvlu*t88Vp^d>NL|Lr`DL?D4N}c{_jp%(HAg{rPUu*Szg# zj!7mc`&s@7H-CTs|F4$!|Ce$kF#Fm5;7{vuU}%<3{UCovtdW7u?A8PO8JbLtJXu!` z)*E=Uq<`n{|J}Oq&G+9=pRT^{A7^iE9sTdm-|J7arQcTAE#7O4&s)AbmEKG-&pNxS zC@bvpTt>gz>5tZEFHbWKWmwE(Czx~Ggt5o$h06xsU>1W{3Bm_|n8_b_(d@&LeEW~i zg^dTlUYFmmyZpo3^85CcxxEL@T$u4k9$$#sDCao5UUz!>`!fG+?(yZBb?@R92k)%- zop#eIy}>bd?)!h82_c(x{)!wp;MSY4tn@sS#GMSmGuvLoGe{eFu^7LrP;BV6C}r84 z_dQ80!}%J=HG4bxbez$5Ys+@0HQnxRMXMf1ieE3d1B&%|CHbgX^da?eHs6`1kLARhjOac zf3;y1Iq~M{LLS3g_M2bU{+PBvomV=FH7$YTy5I%1<5B$=?>3fqI5P%5iajq7)W9SX p;gpazd1JnvZNlx8HB0WjVJ`J~Q+P@%pA+aZ22WQ%mvv4FO#n^cR9FB2 diff --git a/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json b/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json deleted file mode 100644 index e339ac0e..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/appsettings.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "TokenValidation": { - "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] - }, - - "Connections": { - "BotServiceConnection": { - "Assembly": "Microsoft.Agents.Authentication.Msal", - "Type": "MsalAuth", - "Settings": { - "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. - "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. - "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. - "Scopes": [ - "https://api.botframework.com/.default" - ] - } - } - }, - "ConnectionsMap": [ - { - "ServiceUrl": "*", - "Connection": "BotServiceConnection" - } - ], - - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Microsoft.Copilot": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/assets/card.png b/src/samples/Application/messageExtensions.a.searchCommand/assets/card.png deleted file mode 100644 index fa101905980845f502d8b29fdf1d15da7ef758be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76643 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

Uvu3KLcb-LHWGsYv-;01@4Nvez=*>?}5OIuH zJrlOLrF9gU5J(~6#za}t+y?0SbWBsMzmKWymPH&*#Bp0-9dt<>x6-`?y}PuUJRZ3* zL#S@5GYu(GI;CPT#tbzXm3dzrJ3ih-JVR~TKoA2u9`!p}i9U2g3hAf{s=5$(0d1fW5%b*V!XUu8!l;nB9uS$#OruyNzTBOHZM4lZq0yn z?I*Z?Rhq4Hu3{e4#VB378KkX7FEr_sj1UYc#ItXa zqx+ueO%Fh%a4s18@NroUmRUBc8WJ(3OtJd!u66Ek5EJF!rDCW3@;22YIu`i^=}J%3 z8GN4=7zNk|*Y@A)S$}UPiY-t$oN4Ft%mYQ(hxKbI8pQXPmG>0>-LxZ8rqW+Zukt;- zi;L66)$KBWl(Ca$-}^L3#SyA8Yq<+|4Kz(5HPAdPy+_=Yomrs1XZFI{JutD9ddWz4 znKN=aZG&-;QxYC;eWxW_%S)|UHCR(EazG`@L#B!sC2{D6LmY&2ID&ERJ?d8$UGR#_ zbi%D_;nsekB*vTpDjQRHqe{(jPA}@_)YCfq?G}SQU#p ziWWGlirMk>+A%o7ZC_iI=vZQwvHSwVb;r*yZ33+@Ji8r*+6_}Mu_{n#hT3VL)+1#| zbnSqZ@F?7{vd{(V$eyO8@}8%dLIYorK1JOw0@sTzZbyC2;9Yi+`1j!lp&RCAh_(<eb~LN{Gq0eA8w%i6Fe`_#{QV<_V{PG2Dos8a^I*&;8?e9-#R|o)q?X*% zCK&RB>Tt3R#v#*_-X$4N;iLJ$EQwDc#4!@Ul942OG*xRMNAyVHbIRHhXTM@Ln?`e4 z+SPahvLctm-uPy}zQ@b^J_iZ!c8Yx8WW-5jMY7)4qukLgF0_2q!pyjiuLDEXRk&lw(n%yj+=u(_a3tBR|B1c9yorpjn{UB9}~MH(lx1<_ckAq1$2ryh<`l9n?7kY8D`twISVB^ zrdqkN6|yf|II1##3T@ssvHl* zO(Pt4S3|rZaS^bJj(ObGD$Q033OLE1y2dAXE ztFg0j6bvWhuv>{}E|2w2Ue=zbKOQY&E;x?S4NrN|io=`Q$-k%GD7%{_ykbviy;M0)y|hsn+v}DkJ_Quoe8Z%8(K zOx4%Z#Vw~O*At7hgqfe#+s4D!^h3RN65K|KA9!XNn z(0M@)AVTj+f4#<f7wul-{D z9*Mk1^9lEyaN)h>)(WKx!-16uPd0W@eW-c{{u{?C0bWD2&s_2fD2}&qiHDAP{LG?Z zp`Oq1P+Sz_s@JtsSVxx8+38_ujy$dXvgt}ZP;X-Hp-qgTVm5iyPJ9AhO6Kr>oD3Q| zSQN;LZ==lOxki|O@sSyVW6nyR_gicI)(Y#)+m8g^&^%ShGBz1m$yIs8K`?mxDy~HtH(SE$t3>kl z6?BVs?)@-|xsRP0&k#$Wz(<`)#TG)CHJwaScF#X%E?G1Gh{ZAK{-3dGmht{%zCOrV z76}kO=P)u3eCc2a2`2)&4tk{$ctzVAfFCfWLTWK@yyeVy)LTHPya^D)I#ov|Tq^#4 z5OXyscJQ46K+E8Lp5T3}$z9=84Uj7V@Ptq?znW?XTTUHtCau^0tUJj@;VHWeP!U`D zL!c-hgqN7mA!i$EKr^gMP7i*O=Hnv^R=TIrGW3gofw0)FcJU76B%YGX_Y!2evw1V0 z1)$^12(70sKL$~U4w2Be?hk_iQ}}>p;q%UFyV)ryy2|eF(WddM9}C{+Af~Yvntg`= zUE>1i<#pg7t@Z(5SRe5~q#i0q=-4DRZ;)(YE5ak8^1Y|W5eftn0S*YW{L?neU*~reFpRZNXQ3eTEcZp(;c924L}$vFti>^=Nd9; ze7rM>gbY(axjM@lTUcAhobJ))+iqWB;RK=tm)jVe7TtrY29kKXn*cOb3XsU-lz*X^ zW3cREoTWVgO1pp#-d+B_V^FcKR;MPo?5i^DpA^DC0reA#Ek%MsU_5E85WB?2r1ZB1I}RKXJt%-#3?)3-=}LC)kQJ;2slD)R(a zlo>TS^9o=k>kR}q#|U_=5!BXDSrTh=ms6Sw0rTE_&Ug#tB?#Q#CjfG`tsd28n#iJR zKS${KNM>^`7FY}<1EdEo5tnK2BTvz0NiP1Ny!Y>Rt_gq+1;W@Lqp1s0S3E#>4E4AI z$NPe)zwn8SA@H?Xd%gEdLv7M|vDsfs@GL2NfpQN>)*HYCM9L_+Bs=toyMdl_iqp9X zSZsQ>U&GZX(k&5`hMM16?n!zA|ECnVQuRJ*VV+9L)IRm~ISVkvNctRI4{Yxf;j%2I zjxgmtls+Lka*8fB^rag0Y&kdmd$1&R90$iIHato++dBa6TLJvnW9LVENJ4ZSa|{CN z9VqY5{pxIe+8GC&4?snm-D=|yzKCQb`B4ubpY>3w4r=K!8-sF2`_$WXN%)v>i_7Kq z-W7T#k(WT!cQ&T06k^RFg`;bf;c(2{fF4JBn#YZWyX{;lcn4Mu|Bxi=210XJbpj_5 z?UI3al3FjjJ>w=i{ z2JkQGWK~j@HC`fXzxE9+Hp8@ptj61-D$!3+(Jp{9M^&#INKQx&b_H_Yie2BfWN0~~ z0LvN0+hAh#PQ6&d52w*`?6H_Hj(L8~=Bv>-qay+gUmZNm3ovTaa=CmSvRX$vHqpjJ z$yM9}<{NHU9-Sj~xE{&LZ6w!pR-_;_{%~r`7)11ZtsX|$@P z|89vKEp!PN?K{aQs8(5l}XgxJN!9Q zXoVBXP|sl{5e7mMSv-_7hK!Ur(7-Q#b0}es4h===A9YSaty|-7x4I=4W#+Vi&}z zO#|@K_8KDBN?WeH1i*ao-p$FuR3`;r(G0E~6F0MR;sSuUO-=rQt-=wEQI&dw@u`>e z#_2d+z&2(U==gWQa?dAVIe32@s)9jbQ4emv=^30D3Lc9OH;gu*pK9m=OAb=3>^bhzz~%E@|B>NmGs58vPH;mZn-Pm8KAn2 zp(@7&fSSB8l#isg?e^3zfiHq$V;LZVWW<&C`_8bufx!PI;4a!2pjwJfMu*Tl(`%=q z8>)ZmShb2KPKvaO>mecpT6OKfPqPq04>@T$UBapPGp5%7WoYo(%}lgekC*;d1x5t! zP_&J^nyPAqUZq*+o3&J%Z?0cwoRr4DSZSO;zPY5mArm-SXpGt;b4}h2fVQ5NVQxPM zmkvAfJG$XdK$F)(bUFA*0=NUYV5?#>%KlZL-u@f7dszs*0iiRqprkUDatF$RMWTPM zHY~LI*~?mo%!cI%5u2=^VEI`FzG>Ea^;?kC%5a1QV!B%3a!r3m+uiNC!Yjd^n0N}+ zd;`@BU<+`4AmYX=a0{*SpK~l?!mf#dN4WykkNAQMw@yHuc0D9cU=mMrXu~R1jAdF> zue-OVQExL@>0|BeS@fa|86miYc>>C-dMG#qyusI^5OU6fRQhDpjQ{-d063181j=i$ zAG$)mL2p8Isw`G@o{2${^UF){CT$(v*MOQQH6T2#fOhs$aN)k~r=f^G!nJhH7pxV4AbW8X0eDYw(TMII;7(eTNc|{b04Xt1ZK@H;|R-8XPW)dbHm^D6UPB`rZt}EZ8 zkwB36<>c?-V?Vsjn}slrfVF>ELW<;i)S$_$4j8RXnX$nNW0_PcF99Ew4Q|+T8~XU0 z8wEjD7;?q-;aR7bFd!Fis?F9wjOi6N?OR57t|7{_7=2Hh4f=jeA_>+n&;B{q)r6-a z|B9lDllcEzPYaKvAX8>LXvz_k$(1Q$-frZqPgDL6<%~jSV8Sy|;Id%{I)myzK050! zpi{I-OqDs+;^#;F!3HV`*%vsL(50v))hMG4Hupd&TnDJ3yb)J4kMLbEx&Tr~0l5X) zz&KRD9>qR^&LaH-6FN<@$NC%MpvvCUE3NIxE1;0@ZS)2YpwH*4=qt8mqX!{Ou8{E=7@hw)0~!+~#MF9!-hEHPHUm!UHduXGahk!o zQb(&GEFxxBbpab<*fg-{ngKJ5M(wRF*H_-PH7mHlwjFYxv>QwnGDfgqLBqVAn;Cvr z6d^ewm!L5y#`Au`9}m+pk$M9XZ=JM?G_j=90DG7Og|E!!D8{xCLf7P;3@nJ1^cRUF|48GpRz=v^$V_HhIUy+Qyz8P>H$?Xqj-F1B+>QSx- ze9<9|kv`}NXP{YQh}{SEYTRDIM^Vset}%FmwfQsmhuxEXGSF(SLy-^eps1KS0XNs= z9$R-#az~ZR=5wm}-#`-bAkcmhEF38#hHhgeI!Hi;m($rqbiVdzMJKBT_ub&?%1V&@ z>fI*G8A9@7*>6pt8vL5!yg0N`wmsB&*-bR^9!rYG_cW6VR7T{^$TQW#HT%aLaQ-Yop#hecR9q%99-8|F22J(WKr<041qYSuz-y=% zFX660M+u@~qaVRBDyawg=A*19Z_CBk5>02oifAo8jUbT0x|RssAmM)rYoMU4&_Qq@ z(_$co2nmE(H4Z~SzO@Resg#z%kRIpm#E)$G^_Lv6&SS+dhF*e30}1wpulQtAA1!Hd z-}dd|i|#!k9!KaOKXCd^cdQ59i!1rc!9_IG>HN0I)?jhg1G=uynJ3bnh_o1G-zhM( zr=pMwhAocY0Q!a7(H}kmR#x`@z|)SH?n~Nu@vBo)V&c=g5Y^{tg8SnSc<7R<525&O z#&@9i33os*@i}#1Is|=oQth%XrVFe9L|%kaaT@tUL)rbPXAt%PR^)ad4U%7*Cw~Fz zFtHj?z$AZ?Gm>8vS%j1Dc!q$H_jWNbzhh0M^WBAECXY{SVLKF~&mHo}$J{sWZIBFE6QDsd+KTaPIqda6LvtT9&s zqTfCjt7nHii0u)w9xJhW@EN#i4k8l<$%Ym_=Yy3awi8R+;z0BqU>!US5zsPxpphr# z3N}rqjf?V@Zh~uu4RTh)2Mz?7L?! z$X|xi3;cs1&W_hegb+bW8xQ}{ITGfBtrIN-FFW?J6zYh zFPl-wxq(SLL398Xrmw}oWvG^!4=$=Ew0f_P*|+lU{=jRrAUdat0(q<1`sz^S1Fc_0 zPp6pN5gVNQA3DYIp+kPEnuM)Y5)BQ4&)pm|Pb;?4>X!eacSk11ek|-Bkp^4}#XSHl z8Pk>oCM1ifn6+m6fgkZs0$Jb_nC+H_A9x1KS5pZ+S5cNQ`b=C3S$!fZ?wHzkZY4jy zpzeDZk1UAdK&%|04oap&%DJ1;c=#i&=%&9BUOo?sOVozV!eKHiGR#>Oy8X=FaT6CQ z>$e@;!S!T#MksEBc;bWl*7!t|*r*p3W^eGp=+TGAB5}B5eCb@+(yf>&ETRIw#hCgf zP6_%>q3qW|I8P3Cm+!Qt_B@9t%17L{GQKbeqh*)co?~qbI-xw;_Y^FRFMzP9LD+RG zXx^z+jU+R4@2LH_$B2s=uPOLGn*fpyq0+%B-Uc)|ujZaxbPC;^sY>Z2EUgPk3ONpJ zi{NhaV-cxbkXLq`lyo^~&qo3$&KCcO{&vC^1Z%0_LePkf2Sv0C(5KYih{ZY+z@<#$ z;Lz4YdoXrWn#7m&q6JB`Z^6y7tA6v)r8#r5Wf=SD7BeYsy;2pZ5si7R$eRk|ewPZWSks#Zl)T z98+NG6~lS|CF4+UH|xgy`k;fau+(TEc^f)vD+uHx(;;7H?J=tyMTzeM?g%quZRYfp zW1~RdTB$(u(+oVS-9bf5%@u_O&QXSmvuaZh8*Fc-;V_wVRbbPY3M(**S#rB5b64;} zrOL-7Zla>oOFB)IF#RG|{_Lu29gWc;m^@1*!MwIU24M!K%-BQoJYUuev_t?sg36*(}$)yk0YjUuYX!@mtlkb>QM@!yKv-L6s{$|Wyknq5XVZ+5+N$r{Af@J z=XTAVh~=~QhabSbE@Pgh9K;&dYRV{!%c-m%_35zcnuz1dH4RhGV%gNneDaa_AwOG4 zs?LlA$tevzwWkUj&!~vYMwCbD%@5@_<;(7!&zg2P3 zg^r>E?}>+1av&0FU~uTrp}972@4za+=#Y)htQQn^HlzcPC~m~?^wBbP8|oQsBO}5! zNwhtO_d->NhJ;Ab(%-zSTr+kak{UINo`7v?dJFGL7sdnH5BDTNb3B9ZN2#)szAL(F z5|>}jj1FV4pvR@;X%eOuEC)f^@$#VmH;eDFMEhTWb0q0d5=oxgrvqQyHIBc)DRvU~ z5NhSkk5#*{bzU=uRL}6|S zn)u=+(bRdpxMlpIr)M6N=WXe%lx1YT8 z_h}c(w!+zDbV^YQ`k6Ftek-Or`Twwrys~X)vg)Qa2F5PYoH-`Z z3J+&M9kgBVvbn@R=V1!TutU6M6d^Plrg}yt55%yyL3i?QV$P61k;gK^8^l{ePUCA4 z!dpULF1K&pVv|>tkN8oJc-!&=4K>@#{!8;yUxX6-STpa@5mR6#D$qKjpb z?Vh3J#%PY73@OABi$-QWXh=?gCr5)czEB5L+aiGxFy@Ki3DBK0NYICt>c0mg0Ct_oam zGax}hD)-FB786K z4j!wMnZ?F%=9tbT&^)$B^|k}-S$VjudQ8_?;c;9r=z-^!a&sU^5*eqV>Ubk4hZOMJ zfTJlkY-xrREPC}WRvu<&xuDu!qg=_@2T`ILV6N&dd-RtzCbPwf#WsC(IBy`dlV!L%{1I?8x%jcsd93FHF@un ztKB}DaUKemUuz@0i<)r95`Y}W1w7Uh5Rct*1{`T%{pTus(3ag@zKmVs9|l#h|*~ct03NDwSg8f4-q}-(Uz+M9p1mx`p(@SIQMfMrx{B{ zg#UF0GGC5ek$rdI9Xi34GYy=QGDgaV4O2`7zXBZ`f9fW?pzO;8pUP2fy*j9^>9RcL zspz_nH}&wnxIn~$voJr{$tMo@i^f`37JJ#DcETe7DOsHbcz6Lhw^{!|vx}j#O+47ZKEAQ;-dk1*YO)zP}pJYM>X@dyU;3X?54w{E{sq zx+N*F)ldo=3KkHKfk@Qz)c1hx&@n{^!{lFDz+gjV3s4>Ahdv_^bEaf~m6>*Zz>;=m zLyK4yO|h%DB2UJ_V+`jBPI(&JJJ9e@=E0>Ei=o#hqqTku2y=!YJiXx7bgWQ0Z7V9Wz4BZe6RBH7zk93Vr^-a4BM&N}5 zDAjbcDOCai=m|H4XhFSBReArXDc*9td!n*0$!M&6-yw{bdCs%XFyG2I4sS&HH5U8@ z7}7vXEPXYrXHfLS!8V@2jM4bA5RnIPzp75Nyg1!2 zUQwr=H-xBT2snc}xXXw;Sl7}S^5EZZ`dWA=eq6{+g5JpxCd#VdDcuP}z%cUb~+P|R^M)1E%byB!*q8+E)wjZFJ{%TZpOp)~aNfrln8>QLZbPCA{h&(*?Iq}Xwym?>zQ*as9j zE`&lEg6_EHzQZS!{(dUp1e>O`E{Puc{PrlvAast*leCXGFsdcgG~?t<$sjws z5^$UAtKJ>>tKo0r-r2MsGL(9Ry!xA`XL-x@9GJscd7hsxN7nFu!-)Ipf0!R|@FZ

1J7!tgdG=ahc+$)MTb3g~S#iXY5i$SX6{iB}=OcMdvy!LFidCEyK8#8szCfVWq zPmg|23*fRRLgiG2B;!X+*1QhTFcX80Od|^bJE5QaC%GhZ<1krYibVVdPnrr^@a!=< ziCPw5)I&d&GownrG6t-~kjOD$=AWXWwzs{*rHH|#%Te~M8?6Vn#KIKP8S9)(HiJx%uJ7;>x%2$^b2fa!qG8Ic)Y zuHJQgNJw@p3Sdf)xx-svz#$3og1~bVlyx~tN| zHO~S#j~O#K2=9b`LK$>_yu|a$gILluDpA6(IT&UC0q;tD{0f7b@i^m9lzY}0#`mz5 zo=?U#*0LG5147vMp$cQ(2}_tEz9rxe2d<7MH4-?`24%8kgq<&O%!`4YtWxwrlOfG~ z4$9Cj-J5_2xPfP;z6(e(bp*JXp0J0YB}f_|2FQmE{j0t9;h8V=c5f0R^$vW7QNC~z z>=R>hdv}4(n*nH(8v^l#SscOMvd%`Zu$Rimi7q&7a{EFn4xYl zCqvNRD;fwt%&!`9zq)(w9mG#|=jR7^ zw`mVJ*J&j%mPXrX3G>blWF^h-`OL~qo!1k^EYC0R&W|^A245ul;HO77Qe$q&3>RWa z^OW%@V1RpE2z!4uLt@m~2jB;!Ej>g1d@N*fWEo82m$AE#w3>)J4F!ZG%yk7mBfVr0 zl^xs9_{-G#IC76xdVkPFI6tLR? zt$-kv8(xPLc`|j5!=@%T1y)dLnzG$N)0%z*p}~|64ijycpc)dof%_v_oKC5$7QG-BD{6zi}H{ zRh)@AE+tRZf_LAevs6)KgVkoC1e%c*@)BNs0)!q+5?)?@C7bDkE;TOBOz=@dyw+~` z+ghK5dO4;kUKXyY)zj6xagS01lXh?`cq*6nJ%wL=V^_<;*_V2=upJQqgzVy`6Y-VH zeIl+uA(j9Bn{Ox>E5Wxj3N^^>y1d8-*z`qS+h*hMpJkPEW3RBovTCtCinq1`E&_2d zNIm#r7at5bKMXp1z1OppVIO*u!~uI*9{+qkhWYzBbuXGHp?mbJE!iC0^=TOp(p6y`Zd&m zEPUr;!H?$1>yWbcQ}=6Wd0%Nxo*YzQhfKM)ksQ>2d~_lyJ1|W3(QY{9Yk94d^b_>K zJ&xGfn9PH)E(Tg5z0{B0)xV_y`(RT-q)UjHU`%0lca{R6nrX)n{y~K!lCmjN{!;{s*Z*jvvCfQ zqZr@?&s8y!8#^4t)BFpJg0p&A{t&ixjE5H!BOw7lzl0rsFZESgErkrad7kT?{ddFp z)aIl3y?Vk6ZBk(>lWjbYW!QJrxjokkB#eu~ zBZ!BeF}&Ktct~TSuJh$pG!{kBtrNBNA@`%KtPk6J=LLK$*bmf~f(|{^G2QKO$yE-0#39_H8s$$^Q`ZxcPRoaOQm@EZJ1k@NVwV1-Wl>bap(2Jc0ym zWW_M^v~4Jr(UZXzeeq2~o%k75`3Do*yblizC1$x3@n^j2T{Uht({O;#XrlUlkD}fI z;O1{<)>pMYfdRMvkT`^qvX?AM2aTMYP0Xtue_B_N^@p#{U}{H%<9nK+lH}XxIOfP} z=1nzvJjSLp4q_KGN!_nA3_&S0dQp?hps~>;-zjfw^eFSWDs}uJ;o#K@U9-)%>QE(N zf`*++rCxT=3E`9|8XY_oC4%+}l1t(%gVr-CexH&X-O{1R!F{$@`;Ci|V!_ZqrfpQi=G+t2VM4UIVzdD)~8CB@mAf8 z8XjFP+j*4*@<9kH&{tXQ!Ks7fh-v7sO^V)!a)!Ly*#9fw<5B*dUYsa%r~B#Bn`iqZ zW~YI3B6!pvrHrK`WF>QvR8>IGL-rbF5KV@URM+Qgt%SwLuF)5WL39!}ZNn1&9*21s z+$>rj%=Xvm*lm+p@vjc#B;)Iq7gAHZ^_1Sz>y7`3I#Dnb=D@^^w11^^dx@%5asBaw zjZ;S$9&h_qGXvsJq%++!iiqwa>1^cw=IRTi!X0bU^%X%5yPt1GLw4y&6)4xtto0N# zn~imD{03!MUGKCB#(JZTki5V=(3T7+VtFr&VZ6ieIA-2N#@A#bx#nz?aVGX@;!-L;7sx2@z((RHOoxq(N@s#JhqV`i^MPDX@3RzSfNCJn!{ zVdSA8B23Xqa*KNKEy*5;V}r}?aSBCPBJlkzw(Hf)5wgrWw5RZ|STtWo{3EE+gkUy3 z`zqNXf?)y0xTK6FXCIz&at%uh?#qcT;y*FnlvE!6u`+ww zKN=qK>gy8^V@XCk51zZth|WT?#&5<_B#tlcUpo!9j;N(Fu>SHNf}>XuE|CV^V%&X* zL)`mt>BFE9eTeFv?w^VZf2OQpE5BaWKFK+>csMxZ7?%(b@5PFR+eD1ygjlPV;BogRVE_hA>nPDCKy0VgtshC}g zu;Q~81L5RE2Zq-*Y%XeT?(n_MFr!HwjlF@%i-Q}`-DZr1MxbW5RK38t$ zn#hUla)KyMHSOMX0~onD^o^87^W*UvKh>c_UBVuk1S4yDEs?HggyOi8+!u(^B;y{I z4xw#vlVI}^w5!_w4fA~~U+MJ#KIJAo^gSw8Ct((sCtyNH55~r`p5q*t8`^^b|GatO zV2cb^J+Jq2z7&nEowM1OIr^_hWWvpHk%#SqWW#dh7!s)PQLCWHio_D_}^ ziq$(6K~Ii+m;I{=z~AD1bK2T(`pOW_I%;CkT71v9m91wR9&#e_N#DwurBivopRRhg zwng!8TQ3DtsWd1*No9CO^ee7)N%di-xRJ4MFG?&uQAr1iXKI9$FztvS#%idd*$PpH z8sT;aS25}>so)5avrtpQ%G#%U&(S&xX?pXK4}a_MeEbtZly?R99jfAS7N7mWDEugH z5Fk5Yn0G?9pKF!Cr5iy+>(9@HT(&cw70Ss4cV4&#r+m57%<0o}}T zW33G>Wr3(p*1Ik)Vcu2aIuc4d85jvyICeMz9;aUuNBlcpvs1Dk3hAGW`#XlX-na0R zl9#HiW3bVV%aO}03#W{uA5d$HbWn!lwnvMyS?TLGo5v9etBSImpm~jV=1rC#sKQQ@A z)Hks|o3WH(YQnM@i5TP1Va**}`|ns}Txi#x<0<9Sd*P=?`gC`3^8P_t&*8b1gLk6x zF43vm(Itm42Vp@&b3bxex<9sssMQ6L^gXB`4}ZCSv#ru%#qa}49DxiY!;>5wDbA_7 zNBHDK1rp@Znc_jpTeu@pm2y6g_rq3WzbcnM?LJAn@JA~_tzRj`V&`}ZZm4l6tW zGO~}->BYt6*mN@WoHDA}SAhAQxb+qMbYd#wliE`_LU=Lo|L zOIUR)>id-i*!<0J&selMmOxh0?{ zf^Ye?E#aSs?f6d@LHHQ`{0@TEpm={QVTs`yu2CXs9$P$#ubCMTVD;K}FsY z274x}%w%?^AH>#BGu=9{V9{2t1=4B8=fMcB%7TBSDcth^q^e@7+dQlz0Da z4POoLdq5Chc$lvGq+T|y{L3_Y*hcJYmu%r-3Ue| zx2tXh)!8|=fVXPIcDbL*_$S|w4)zu-VwI62`99GDH=ia{akr@H9OkHJqdoAuJPk%R zxv;+2H@m^broXOQ-4@mV%q#Bk(~JXI#h*%m-dM}ITV%eJ-MhyaPsdk38Cd&JHFR28 z-78M~<~1YGYmUJ-`9AVNdjfp}VSa~g9*dYZ0rXeLn@@oL+ABlovm@h1_s@GvsN=*} z!0n-=>DeH6oy>`L$$bN2^%CUzh|KB>nW~#qmGCZa zI6IC;UJ_ip;*dQih%CKNSR+oIHGM`#;K?IJsrVY*f~K2N?o=V>29b@8)= zQ_~|J*5gPZD%d-+vW%k9HmsE-YF@E@x#lr`9ovIkR>Kxoyo7mw{#PXKC8T_QRS4sQOL$`742q?akvR z+Y~uuJe6J@b>x@o-xcqs>-#t+M{FBZq+jH|fAAb8hqkny9m(8qcGx>lcF9&Rg8%uSBDJu2LKe>d= z?9ZNmx^7`oeLYsPH7H`q%vi5Igik-^0cG9c&}=`luu>!2OKbdf z${l15F~1XFuejX9v086*xY3VBOB?@o>771nntEFN0@8V?<+d--_01027QdSx!I=M9 z){=w-YKf)#`z_Ju+q>v%sKW)uD&r}(>xuH+9}iN15XUMxdh}jR zkYUJxo0_=TSkvB!Vd<_&>`YW<)yu#;e6|zG&U7oKO~#4mTt^kD+^!YC9$c|httFHR zaoLow1uscWBL-J{p&A-i{RU7w#pWgYANw@IWYLjG1+i|+#^ z@5o4z3(eT&UM5U*&xO$ZuG$2{0kNS1iM2Y5R<3ZW(cXlJ9B`@zT*U%Kzjdv^O8(58 zTcmtmH1ImJKbau*)AdD7PdcKbsh)Rb$5BccO%m^DU7*GLuF=8a7>C_Es#5d8g`@eP zruapfSMxF=#jlIQfdj*8Uw_E4^hA$X|2mQxV_knq*Y4%~XhHP!xC(QbG%vvpq03(L znOlhWY4nfM@$GAPW9ev)cJs1vZ(Jv`dTx1EW9UQ0_A)_)-$A_In1DMv!CAD^&hFtM zN1l#vn^=xll;3-4$7KU(1%(0sql4dvMN!q9>z29;e z;dI)1L3Q+qt)=+K$kF6VT645`D&(dn9bp+S|{Njq3u3co<6$xWfC?Tpk90k-p`eQT_Tw755if zmWGww?hIv)d9a!xXjXNxe%Ct_kK5&U*&V0m@*h(5S$_RwoF>Ti;)37apqp||nVie1 z^n4+^XAOVvThnxd-HGikHS%jz6^KPYm@YOn6`jLJp&?A#7%nqaMKOh)9v=fy2g6EqwU)L=ngWE>MPKySf(`> z<__st+Pt1;V`Y#Vbc-s)%O6GU&{bvo}wR+UKhlJX|{y7^TXX`!~pOtVP4oE2|$l{3&Lw%q3( zxN`EdF4O93Gv0oJu&e7#9y_hneIk2%o4mdL6u;eVnx8Y12A%DcOP-IQZF^0pup;V` z%PSqI^0t^*#tlRS1}Ely6RKxW&iSf#5n3>B^tZQUbC$YixDeh!+z#n(BK%z2tfacy zI*Bw0QyKQyYo3ZF-qCh5zWmhwb^Ec(&SAff(vFYNTPXuleHs?Om6VyGLEHKsO3!ghev=3t`|01m zI+y~_L_5tIX5IT-2gtch?9x}Y!LxdnIaAXH-^Ld)6Can*31=jV9nR1UprQw3l^%4o#FEwFJf=1oa-T{HbiNc?XBTK$Bo?-h zTkqb`DOOae+3)((W?x53t=JweP_#aVC0m5CSfSFVzkN{hq~qRK1NJnW64z6v%4S<$ zBuAqQ;!K1-fY7KapppAcmkDvMt^2DuW(PE-N_0=b`|3{?nDaC9YdxEc11U=#qX@Ei zab>srlavOg-Wxh?Kk=Y{)^h0^@;R;lu5nHphIJWP>uFhrQy0M*@-5@MQBMrXS{GE& z_^2a;S(i|YvBs!EwsgLTG9P)uuy|wO^S31AgCgHeUY*R&PQ{ygAq(B?&K>BZFpASp zGE9Yv1MzL*ovfREeU(gQZ>hg^#Vvj{UR=Xptj(=#qev=_si5%HfVqlA0YgSpl9K}e z`nmldC6Zm6TBrV`s$XcX;gnkX;ZI)+v12Yd&RyuAAf~EuB2q>Mw;O)f$A6}Mo59mo zCqIl;e|nI)Hvd5(Ji^BzTyxyQl$#<=4IK7r81@fgo`;iXepFKq^sy3h;pt?^M-&Ld zAToO-1)tX{T4Yv%UQKebhDeRpjAdD#q;!L0uWd>^-cI6ys>_4X1mGfb6@r1B?}vTd zY(ry&N5^8KDYQPo8>zoat|K0LuFmoOwtkKF=OW~=^q2|-IPwRaC6S+2oq}IhaZAno z!WAJF3jUCo;AUWnI*lS};8o(S=icg>l0pM}5S`9(u^NJ#W-eZC9du?4T)JGCsWblm zO${6b@;o4<2`;Rou$c3k&$BSzyzKH2utXMx^?P%D@lcGwq9sU#QkVLi6~OmF6~(dNte8cnyR2o^?OyU{4H5ghDy)>e zB}AB$GdkxmLv3>F&su$a*b(a!>e*|#X{y0kHSbZ2AC@$^Ay+C4Lxu+RlIW6RR{khI z5#-5ypI~cLm#e&_?H^?hY+9R|NZ{ac=={`%sU;Uyv9!31h%{l9kmCqRtmSdpfLUwq zzv#dg`8%Zbxg5R=FZ+l^ql{U%oj$*#Rd~wJo%y~-8;?e)8&;}Lo_DkzP{}*AZn%c8 zf$B%p*;w)DZ;@up(T|SMu$d1OXTiGWMt$sC(_RUn2mF@jW8vgHAD%_pAM%6lu%dCWYK9Rd#E-riIfDqPS8AWY~5I6t9XX z^N9NN&z!Y2zqgl!E2a7!pYfhZ5?YEsKJDxG(HqSq_Y`|si%)!*Wha37%e>Et!=H~# z*M52!jVJBC&+}~a3>P!msL)vL-w3Emlvs6Y^_wuQsRk9(**kxVgU;8zQp*MoGGrH) z#$G|#QpfoVtAz?&eguD;Err(u$#OKhy!zx}PFszt$I}5{5T9^UBymy8o zx%@Nfjf=ML`K3fvzmwl>iHxo0Q>S_?rlolEJO$GZ=FQ(fH}mPLcFb?qHH+w$+TI~M`h9U?<^-m8%k>Fy_232lC<)t?cm8l^VVolea&3@Lb~^28Eu+ntr~`^ z>*ZY9D}bLk>96Cp*R+rFO6bB$a)?%AmL;&Snnx|Os3eTvPB&c^{gD;ECx8T7m6>X= z?p_Nm;d@@ELL;FE2Cnr#BfJ@ z`mMEMe73gcPj{G+BgMy6GjwTPL-zmV=R*nhMzK~rNVz<*-csGFbQJHSty&lostW$h z7&?l;GX7|5^$=~%w&n|nJsa*1Nw4@oWY`$EgDjYs<#g89IHC zOkI-ZdGe+usrnR>_v6aEqyX^wg`i+I`6H(P0g>sYfl2RW zntI&rBM5)}?$Wyc&Yv&=&BCMaH@FG^KiEotnux%~%FL3v>u-CY+WxQDP%>u>9VUs- z26=ja{HpwBv>0>rE0Noy`B}4C;RdPyTzrn>O|+t5zGq zAY}X-3*7@jgt-Ci-aFn;~l zm)j7g1Vp`QpCo@|sUH-Mq^9bVH2BDd>$g%mTKw~|^u=hpKha))68rJ#)en}l8?X{; nPH-sK$!s>+l~83p_=W+_J- diff --git a/src/samples/Application/messageExtensions.a.searchCommand/assets/search.png b/src/samples/Application/messageExtensions.a.searchCommand/assets/search.png deleted file mode 100644 index 90438a883199ada4eee6243e973e32bbdfd30aac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61027 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 diff --git a/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev b/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev deleted file mode 100644 index 7342a87d..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/env/.env.dev +++ /dev/null @@ -1,18 +0,0 @@ -# This file includes environment variables that will be committed to git by default. - -# Built-in environment variables -TEAMSFX_ENV=dev - -# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -RESOURCE_SUFFIX= - -# Generated during provision, you can also add your own variables. -BOT_ID= -TEAMS_APP_ID= -TEAMS_APP_TENANT_ID= -BOT_AZURE_APP_SERVICE_RESOURCE_ID= -BOT_DOMAIN= - -APP_NAME_SUFFIX=dev diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep deleted file mode 100644 index 8bf8a23f..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.bicep +++ /dev/null @@ -1,74 +0,0 @@ -@maxLength(20) -@minLength(4) -@description('Used to generate names for all resources in this file') -param resourceBaseName string - -@description('Required when create Azure Bot service') -param botAadAppClientId string - -@secure() -@description('Required by Bot Framework package in your bot project') -param botAadAppClientSecret string - -param webAppSKU string - -@maxLength(42) -param botDisplayName string - -param serverfarmsName string = resourceBaseName -param webAppName string = resourceBaseName -param location string = resourceGroup().location - -// Compute resources for your Web App -resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { - kind: 'app' - location: location - name: serverfarmsName - sku: { - name: webAppSKU - } -} - -// Web App that hosts your bot -resource webApp 'Microsoft.Web/sites@2021-02-01' = { - kind: 'app' - location: location - name: webAppName - properties: { - serverFarmId: serverfarm.id - httpsOnly: true - siteConfig: { - alwaysOn: true - appSettings: [ - { - name: 'WEBSITE_RUN_FROM_PACKAGE' - value: '1' // Run Azure APP Service from a package file - } - { - name: 'BOT_ID' - value: botAadAppClientId - } - { - name: 'BOT_PASSWORD' - value: botAadAppClientSecret - } - ] - ftpsState: 'FtpsOnly' - } - } -} - -// Register your web service as a bot with the Bot Framework -module azureBotRegistration './botRegistration/azurebot.bicep' = { - name: 'Azure-Bot-registration' - params: { - resourceBaseName: resourceBaseName - botAadAppClientId: botAadAppClientId - botAppDomain: webApp.properties.defaultHostName - botDisplayName: botDisplayName - } -} - -// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. -output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id -output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json b/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json deleted file mode 100644 index 1b12ffc8..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/infra/azure.parameters.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{BOT_ID}}" - }, - "botAadAppClientSecret": { - "value": "${{SECRET_BOT_PASSWORD}}" - }, - "webAppSKU": { - "value": "B1" - }, - "botDisplayName": { - "value": "SearchCommandInfra" - } - } -} \ No newline at end of file diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md deleted file mode 100644 index e912ea21..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/README.md +++ /dev/null @@ -1 +0,0 @@ -The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. diff --git a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep b/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep deleted file mode 100644 index ab67c7a5..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/infra/botRegistration/azurebot.bicep +++ /dev/null @@ -1,37 +0,0 @@ -@maxLength(20) -@minLength(4) -@description('Used to generate names for all resources in this file') -param resourceBaseName string - -@maxLength(42) -param botDisplayName string - -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param botAadAppClientId string -param botAppDomain string - -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - } - sku: { - name: botServiceSku - } -} - -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' - properties: { - channelName: 'MsTeamsChannel' - } -} diff --git a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml deleted file mode 100644 index d86cb665..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.local.yml +++ /dev/null @@ -1,91 +0,0 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json -# -# The teamsapp.local.yml composes automation tasks for Teams Toolkit when running locally. -# This file is used when Debugging (F5) from Visual Studio or with the TeamsFx CLI commands. -# i.e. `teamsfx provision --env local` or `teamsfx deploy --env local`. -# -# You can customize this file. -# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. -# Visit https://aka.ms/teamsfx-actions for details on actions -version: 1.1.0 - -environmentFolderPath: ./env - -# Defines what the `provision` lifecycle step does with Teams Toolkit. -# Runs during 'Teams Toolkit -> Prepare Teams App Dependencies' or run manually using `teamsfx provision --env local`. -provision: - - # Automates the creation of a Teams app registration and saves the App ID to an environment file. - - uses: teamsApp/create - with: - name: SearchCommand${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - teamsAppId: TEAMS_APP_ID - - # Automates the creation an Azure AD app registration which is required for a bot. - # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. - - uses: botAadApp/create - with: - name: SearchCommand${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - botId: BOT_ID - botPassword: SECRET_BOT_PASSWORD - - # Provides the Teams Toolkit .env file values to the apps runtime settings so they can be accessed in source code. - - uses: file/createOrUpdateJsonFile - with: - target: ./appsettings.Development.json - content: - BOT_ID: ${{BOT_ID}} - BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} - - # Automates the creation and configuration of a Bot Framework registration which is required for a bot. - # This configures the bot to use the Azure AD app registration created in the previous step. - # Teams Toolkit uses the Visual Studio Dev Tunnel URL and updates BOT_ENDPOINT when debugging (F5). - - uses: botFramework/create - with: - botId: ${{BOT_ID}} - name: SearchCommand - messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages - description: "" - channels: - - name: msteams - - # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. - - uses: teamsApp/validateManifest - with: - manifestPath: ./appPackage/manifest.json - - # Automates the creation of a Teams app package (.zip). - - uses: teamsApp/zipAppPackage - with: - manifestPath: ./appPackage/manifest.json - outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - - # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. - - uses: teamsApp/validateAppPackage - with: - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. - # This action ensures that any manifest changes are reflected when launching the app again in Teams. - - uses: teamsApp/update - with: - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Provides the debug profile to launch the app in Teams when debugging (F5) from Visual Studio. - - uses: file/createOrUpdateJsonFile - with: - target: ./Properties/launchSettings.json - content: - profiles: - Microsoft Teams (browser): - commandName: "Project" - dotnetRunMessages: true - launchBrowser: true - launchUrl: "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - applicationUrl: "http://localhost:5130" - environmentVariables: - ASPNETCORE_ENVIRONMENT: "Development" - hotReloadProfile: "aspnetcore" diff --git a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml b/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml deleted file mode 100644 index 9b5e1d91..00000000 --- a/src/samples/Application/messageExtensions.a.searchCommand/teamsapp.yml +++ /dev/null @@ -1,83 +0,0 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json -# -# The teamsapp.yml composes automation tasks for Teams Toolkit when running other environment configurations. -# This file is used when selecting the Provision, Deploy menu items in the Teams Toolkit menu for Visual Studio -# or with the TeamsFx CLI commands. -# i.e. `teamsfx provision --env {environment name}` or `teamsfx deploy --env {environment name}`. -# -# You can customize this file. -# Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. -# Visit https://aka.ms/teamsfx-actions for details on actions -version: 1.1.0 - -environmentFolderPath: ./env - -# Defines what the `provision` lifecycle step does with Teams Toolkit. -# Runs with the Provision menu or CLI using `teamsfx provision --env {environment name}`. -provision: - - # Automates the creation of a Teams app registration and saves the App ID to an environment file. - - uses: teamsApp/create - with: - name: SearchCommand${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - teamsAppId: TEAMS_APP_ID - - # Automates the creation an Azure AD app registration which is required for a bot. - # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file. - - uses: botAadApp/create - with: - name: SearchCommand${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - botId: BOT_ID - botPassword: SECRET_BOT_PASSWORD - - # Automates the creation of infrastructure defined in ARM templates to host the bot. - # The created resource IDs are saved to an environment file. - - uses: arm/deploy - with: - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} - templates: - - path: ./infra/azure.bicep - parameters: ./infra/azure.parameters.json - deploymentName: Create-resources-SearchCommand-${{TEAMSFX_ENV}} - bicepCliVersion: v0.9.1 - - # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. - - uses: teamsApp/validateManifest - with: - manifestPath: ./appPackage/manifest.json - - # Automates creating a final app package (.zip) by replacing any variables in the manifest.json file for the current environment. - - uses: teamsApp/zipAppPackage - with: - manifestPath: ./appPackage/manifest.json - outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - - # Optional: Automates an app package check for errors that would prevent the app from being published and reports any problems. - - uses: teamsApp/validateAppPackage - with: - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Automates updating the Teams app manifest in Teams Developer Portal using the App ID from the mainfest file. - # This action ensures that any manifest changes are reflected when launching the app again in Teams. - - uses: teamsApp/update - with: - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - -# Defines what the `deploy` lifecycle step does with Teams Toolkit. -# Runs with the Deploy menu item or CLI using `teamsfx deploy --env {environment name}`. -deploy: - - # Install any dependencies and build the web app using .NET CLI. - - uses: cli/runDotnetCommand - with: - args: publish --configuration Release --runtime win-x86 --self-contained - - # Deploy to an Azure App Service using the artifact created in the above step. - - uses: azureAppService/zipDeploy - with: - artifactFolder: bin/Release/net6.0/win-x86/publish - resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} diff --git a/src/samples/Application/messaging.echoBot/BotController.cs b/src/samples/Application/messaging.echoBot/BotController.cs deleted file mode 100644 index 70a1ebb4..00000000 --- a/src/samples/Application/messaging.echoBot/BotController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Agents.Hosting.AspNetCore; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder; - -namespace EchoBot -{ - // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. - // When called, the request has already been authorized and credentials and tokens validated. - [Authorize] - [ApiController] - [Route("api/messages")] - public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase - { - [HttpPost] - public Task PostAsync(CancellationToken cancellationToken) - => adapter.ProcessAsync(Request, Response, bot, cancellationToken); - - } -} diff --git a/src/samples/Application/messaging.echoBot/EchoBot.csproj b/src/samples/Application/messaging.echoBot/EchoBot.csproj deleted file mode 100644 index f7a76b85..00000000 --- a/src/samples/Application/messaging.echoBot/EchoBot.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net8.0 - latest - disable - - - - - - - - - - - - - - Always - - - - - - - - diff --git a/src/samples/Application/messaging.echoBot/EchoBot.sln b/src/samples/Application/messaging.echoBot/EchoBot.sln deleted file mode 100644 index b3272ef6..00000000 --- a/src/samples/Application/messaging.echoBot/EchoBot.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.33906.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoBot", "EchoBot.csproj", "{D045C9A3-F421-4E8B-91D0-33A62C61DCCD}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.Build.0 = Release|Any CPU - {D045C9A3-F421-4E8B-91D0-33A62C61DCCD}.Release|Any CPU.Deploy.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1A3065E4-A54D-45EE-BDCB-1BADCD6EA7CA} - EndGlobalSection -EndGlobal diff --git a/src/samples/Application/messaging.echoBot/Program.cs b/src/samples/Application/messaging.echoBot/Program.cs deleted file mode 100644 index 5aab8ae7..00000000 --- a/src/samples/Application/messaging.echoBot/Program.cs +++ /dev/null @@ -1,78 +0,0 @@ -using EchoBot; -using Microsoft.Agents.BotBuilder.App; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; -using Microsoft.Agents.Hosting.AspNetCore; -using Microsoft.Agents.Samples; -using Microsoft.Agents.Storage; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddHttpClient(); -builder.Logging.AddConsole(); - -// Add AspNet token validation -builder.Services.AddBotAspNetAuthentication(builder.Configuration); - -// Add bot routes and logic -builder.AddBot(sp => -{ - var options = new ApplicationOptions() - { - StartTypingTimer = false, - TurnStateFactory = () => new TurnState(sp.GetService()) - }; - - var app = new Application(options); - - // Display a welcome message - app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); - } - } - }); - - // Listen for user to say "/reset" and then delete conversation state - app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); - }); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => - { - // Increment count state. - int count = turnState.Conversation.IncrementMessageCount(); - - await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); - }); - - return app; -}); - - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.MapGet("/", () => "Microsoft Agents SDK Sample"); - app.UseDeveloperExceptionPage(); - app.MapControllers().AllowAnonymous(); -} -else -{ - app.MapControllers(); -} -app.Run(); - diff --git a/src/samples/Application/messaging.echoBot/README.md b/src/samples/Application/messaging.echoBot/README.md deleted file mode 100644 index 652fadc8..00000000 --- a/src/samples/Application/messaging.echoBot/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Teams Conversation Bot - -Teams AI Conversation Bot sample for Teams. - -This sample shows how to incorporate basic conversational flow into a Teams application. -It also illustrates a few of the Teams specific calls you can make from your bot. - -## Set up instructions - -All the samples in the C# .NET SDK can be set up in the same way: You can find the step by step instructions here: - [Setup Instructions](../README.md). - -## Interacting with the Bot - -At this point you should have set up the bot and installed it in Teams. You can interact with the bot by sending it a message. - -Here's a sample interaction with the bot: - -![Sample interaction](assets/helloworld.png) - -You can reset the message count by sending the bot the message `/reset`. - -![Reset interaction](assets/reset.png) - -## Deploy to Azure - -You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. - -You can find deployment instructions [here](../README.md#deploy-to-azure). \ No newline at end of file diff --git a/src/samples/Application/messaging.echoBot/appsettings.json b/src/samples/Application/messaging.echoBot/appsettings.json deleted file mode 100644 index e339ac0e..00000000 --- a/src/samples/Application/messaging.echoBot/appsettings.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "TokenValidation": { - "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] - }, - - "Connections": { - "BotServiceConnection": { - "Assembly": "Microsoft.Agents.Authentication.Msal", - "Type": "MsalAuth", - "Settings": { - "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. - "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. - "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. - "Scopes": [ - "https://api.botframework.com/.default" - ] - } - } - }, - "ConnectionsMap": [ - { - "ServiceUrl": "*", - "Connection": "BotServiceConnection" - } - ], - - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Microsoft.Copilot": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} \ No newline at end of file diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs deleted file mode 100644 index d18b6c3c..00000000 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.BotBuilder.UserAuth.TokenService; -using Microsoft.Agents.Core.Models; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace AuthenticationBot -{ - public class AuthBot : ActivityHandler - { - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly OAuthFlow _flow; - private readonly PrivateConversationState _botState; - - public AuthBot(IConfiguration configuration, PrivateConversationState conversationState, ILogger logger) - { - _logger = logger ?? NullLogger.Instance; - _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - _botState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); - _flow = new OAuthFlow(new OAuthSettings() { ConnectionName = _configuration["ConnectionName"] }); - } - - protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) - { - // Display a welcome message - foreach (var member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken); - } - } - } - - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - if (string.Equals("logout", turnContext.Activity.Text, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogInformation("User signing out"); - - await _flow.SignOutUserAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); - } - else - { - TokenResponse tokenResponse; - - var state = _botState.GetValue("flowState", () => new FlowState()); - if (!state.FlowStarted) - { - tokenResponse = await _flow.BeginFlowAsync(turnContext, null, cancellationToken); - - // If a TokenResponse is returned, there was a cached token already. Otherwise, start the process of getting a new token. - if (tokenResponse == null) - { - var expires = DateTime.UtcNow.AddMilliseconds(_flow.Settings.Timeout ?? TimeSpan.FromMinutes(15).TotalMilliseconds); - - state.FlowStarted = true; - state.FlowExpires = expires; - } - else - { - _logger.LogInformation("User is already signed in"); - await turnContext.SendActivityAsync(MessageFactory.Text("You are still logged in."), cancellationToken); - } - } - else - { - _logger.LogInformation("Exchange user magic code for a token"); - - // For non-Teams bots, the user sends the "magic code" that will be used to exchange for a token. - tokenResponse = await OnContinueFlow(turnContext, cancellationToken); - } - - if (tokenResponse != null) - { - await turnContext.SendActivityAsync(MessageFactory.Text($"Here is your token {tokenResponse.Token}"), cancellationToken); - } - } - } - - protected override Task OnTokenResponseEventAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - _logger.LogInformation("Response Event Activity (Not handled)."); - return Task.CompletedTask; - } - - protected override async Task OnSignInInvokeAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - _logger.LogInformation("Exchange Teams code for a token"); - - // Teams will send the bot an "Invoke" Activity that contains a value that will be exchanged for a token. - await OnContinueFlow(turnContext, cancellationToken); - } - - private async Task OnContinueFlow(ITurnContext turnContext, CancellationToken cancellationToken) - { - TokenResponse tokenResponse = null; - - var state = _botState.GetValue("flowState", () => new FlowState()); - - try - { - tokenResponse = await _flow.ContinueFlowAsync(turnContext, state.FlowExpires, cancellationToken); - if (tokenResponse != null) - { - await turnContext.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken); - } - else - { - await turnContext.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken); - } - } - catch (TimeoutException) - { - await turnContext.SendActivityAsync(MessageFactory.Text("You did not respond in time. Please try again."), cancellationToken); - } - - state.FlowStarted = false; - return tokenResponse; - } - } - - class FlowState - { - public bool FlowStarted = false; - public DateTime FlowExpires = DateTime.MinValue; - } -} diff --git a/src/samples/AuthenticationBot/AuthenticationBot.csproj b/src/samples/AuthenticationBot/AuthenticationBot.csproj index 75cf8b34..0de58ace 100644 --- a/src/samples/AuthenticationBot/AuthenticationBot.csproj +++ b/src/samples/AuthenticationBot/AuthenticationBot.csproj @@ -13,8 +13,20 @@ + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + true + PreserveNewest + Always + true + PreserveNewest diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index d77df3ee..9ccca199 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -1,18 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.UserAuth; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.UserAuth.TokenService; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Agents.Hosting.AspNetCore; -using AuthenticationBot; -using Microsoft.Agents.Extensions.Teams.Compat; -using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Compat; +using System.Threading.Tasks; var builder = WebApplication.CreateBuilder(args); @@ -25,21 +27,100 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add basic bot functionality -builder.AddBot(); +// Create the bot as a transient. +builder.Services.AddTransient(sp => new TurnState(sp.GetService())); +builder.AddBot((sp) => +{ + var adapter = sp.GetService(); + var storage = sp.GetService(); + + var authOptions = new UserAuthenticationOptions() + { + // Auto-SignIn will use this OAuth flow + Default = "graph", -// Add IStorage for turn state persistence -builder.Services.AddSingleton(); + AutoSignIn = (context, cancellationToken) => + { + return Task.FromResult(context.Activity.Text == "auto"); + }, -builder.Services.AddTransient(); + Handlers = + [ + new OAuthAuthentication( + "graph", + new OAuthSettings() + { + ConnectionName = builder.Configuration["ConnectionName"] + }, + storage)] + }; -builder.Services.AddSingleton((sp) => -{ - return - [ - new AutoSaveStateMiddleware(true, new PrivateConversationState(sp.GetService())), - new TeamsSSOTokenExchangeMiddleware(sp.GetService(), builder.Configuration["ConnectionName"]) - ]; + var appOptions = new ApplicationOptions() + { + Adapter = adapter, + StartTypingTimer = true, + UserAuthentication = authOptions, + TurnStateFactory = () => sp.GetService() + }; + + var app = new Application(appOptions); + + app.Authentication.OnUserSignInSuccess(async (turnContext, turnState, flowName, tokenResponse, cancellationToken) => + { + await turnContext.SendActivityAsync($"Successfully logged in to '{flowName}'", cancellationToken: cancellationToken); + }); + + app.Authentication.OnUserSignInFailure(async (turnContext, turnState, flowName, response, cancellationToken) => + { + await turnContext.SendActivityAsync($"Failed to login to '{flowName}': {response.Error.Message}", cancellationToken: cancellationToken); + }); + + app.OnMessage("/signin", async (turnContext, turnState, cancellationToken) => + { + await app.Authentication.GetTokenOrStartSignInAsync(turnContext, turnState, "graph", cancellationToken); + }); + + // Listen for user to say "/reset" and then delete state + app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnState.User.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); + }); + + // Listen for user to say "/signout" and then delete cached token + app.OnMessage("/signout", async (turnContext, turnState, cancellationToken) => + { + await app.Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); + await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); + }); + + // Display a welcome message + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type 'auto' to demonstrate Auto SignIn. Type '/signin' to sign in for graph. Type '/signout' to sign-out. Anything else will be repeated back."), cancellationToken); + } + } + }); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => + { + if (turnContext.Activity.Text == "auto") + { + await turnContext.SendActivityAsync($"Successfully logged in to '{app.Authentication.Default}', token length: {turnState.Temp.AuthTokens[app.Authentication.Default].Length}", cancellationToken: cancellationToken); + } + else + { + await turnContext.SendActivityAsync($"You said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + } + }); + + return app; }); var app = builder.Build(); diff --git a/src/samples/CopilotStudioClientSample/AddTokenHandler.cs b/src/samples/CopilotStudioClient/CopilotStudioClient/AddTokenHandler.cs similarity index 100% rename from src/samples/CopilotStudioClientSample/AddTokenHandler.cs rename to src/samples/CopilotStudioClient/CopilotStudioClient/AddTokenHandler.cs diff --git a/src/samples/CopilotStudioClientSample/ChatConsoleService.cs b/src/samples/CopilotStudioClient/CopilotStudioClient/ChatConsoleService.cs similarity index 100% rename from src/samples/CopilotStudioClientSample/ChatConsoleService.cs rename to src/samples/CopilotStudioClient/CopilotStudioClient/ChatConsoleService.cs diff --git a/src/samples/CopilotStudioClientSample/CopilotStudioClientSample.csproj b/src/samples/CopilotStudioClient/CopilotStudioClient/CopilotStudioClient.csproj similarity index 82% rename from src/samples/CopilotStudioClientSample/CopilotStudioClientSample.csproj rename to src/samples/CopilotStudioClient/CopilotStudioClient/CopilotStudioClient.csproj index d929bff8..9bf45356 100644 --- a/src/samples/CopilotStudioClientSample/CopilotStudioClientSample.csproj +++ b/src/samples/CopilotStudioClient/CopilotStudioClient/CopilotStudioClient.csproj @@ -17,8 +17,7 @@ - - + diff --git a/src/samples/CopilotStudioClientSample/Program.cs b/src/samples/CopilotStudioClient/CopilotStudioClient/Program.cs similarity index 100% rename from src/samples/CopilotStudioClientSample/Program.cs rename to src/samples/CopilotStudioClient/CopilotStudioClient/Program.cs diff --git a/src/samples/CopilotStudioClientSample/Properties/launchSettings.TEMPLATE.json b/src/samples/CopilotStudioClient/CopilotStudioClient/Properties/launchSettings.TEMPLATE.json similarity index 100% rename from src/samples/CopilotStudioClientSample/Properties/launchSettings.TEMPLATE.json rename to src/samples/CopilotStudioClient/CopilotStudioClient/Properties/launchSettings.TEMPLATE.json diff --git a/src/samples/CopilotStudioClientSample/README.md b/src/samples/CopilotStudioClient/CopilotStudioClient/README.md similarity index 100% rename from src/samples/CopilotStudioClientSample/README.md rename to src/samples/CopilotStudioClient/CopilotStudioClient/README.md diff --git a/src/samples/CopilotStudioClientSample/SampleConnectionSettings.cs b/src/samples/CopilotStudioClient/CopilotStudioClient/SampleConnectionSettings.cs similarity index 100% rename from src/samples/CopilotStudioClientSample/SampleConnectionSettings.cs rename to src/samples/CopilotStudioClient/CopilotStudioClient/SampleConnectionSettings.cs diff --git a/src/samples/CopilotStudioClientSample/appsettings.json b/src/samples/CopilotStudioClient/CopilotStudioClient/appsettings.json similarity index 100% rename from src/samples/CopilotStudioClientSample/appsettings.json rename to src/samples/CopilotStudioClient/CopilotStudioClient/appsettings.json diff --git a/src/samples/EvalClient/AddTokenHandler.cs b/src/samples/CopilotStudioClient/EvalClient/AddTokenHandler.cs similarity index 100% rename from src/samples/EvalClient/AddTokenHandler.cs rename to src/samples/CopilotStudioClient/EvalClient/AddTokenHandler.cs diff --git a/src/samples/EvalClient/Data/Evaluation Dataset.csv b/src/samples/CopilotStudioClient/EvalClient/Data/Evaluation Dataset.csv similarity index 99% rename from src/samples/EvalClient/Data/Evaluation Dataset.csv rename to src/samples/CopilotStudioClient/EvalClient/Data/Evaluation Dataset.csv index 3021abb5..445b15fc 100644 --- a/src/samples/EvalClient/Data/Evaluation Dataset.csv +++ b/src/samples/CopilotStudioClient/EvalClient/Data/Evaluation Dataset.csv @@ -1,3 +1,3 @@ -Name,Test Utterance,Expected Response,Sources -Evaluation 1 name,This is the first question asked.,This is the first, expected, optimal response or "ground truth".,https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_1.pdf;https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_2.pdf -Evaluation 2 name,This is the second question asked.,This is the second, expected, optimal response or "ground truth".https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_3.pdf +Name,Test Utterance,Expected Response,Sources +Evaluation 1 name,This is the first question asked.,This is the first, expected, optimal response or "ground truth".,https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_1.pdf;https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_2.pdf +Evaluation 2 name,This is the second question asked.,This is the second, expected, optimal response or "ground truth".https://tenant.sharepoint.com/library/Document%20Library%201/souce_document_3.pdf diff --git a/src/samples/EvalClient/EvalClient.csproj b/src/samples/CopilotStudioClient/EvalClient/EvalClient.csproj similarity index 83% rename from src/samples/EvalClient/EvalClient.csproj rename to src/samples/CopilotStudioClient/EvalClient/EvalClient.csproj index 467064b6..8de6a560 100644 --- a/src/samples/EvalClient/EvalClient.csproj +++ b/src/samples/CopilotStudioClient/EvalClient/EvalClient.csproj @@ -1,39 +1,38 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - - + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/samples/EvalClient/EvalClient.sln b/src/samples/CopilotStudioClient/EvalClient/EvalClient.sln similarity index 100% rename from src/samples/EvalClient/EvalClient.sln rename to src/samples/CopilotStudioClient/EvalClient/EvalClient.sln diff --git a/src/samples/EvalClient/EvalClientConfig.cs b/src/samples/CopilotStudioClient/EvalClient/EvalClientConfig.cs similarity index 100% rename from src/samples/EvalClient/EvalClientConfig.cs rename to src/samples/CopilotStudioClient/EvalClient/EvalClientConfig.cs diff --git a/src/samples/EvalClient/EvalDataset.cs b/src/samples/CopilotStudioClient/EvalClient/EvalDataset.cs similarity index 100% rename from src/samples/EvalClient/EvalDataset.cs rename to src/samples/CopilotStudioClient/EvalClient/EvalDataset.cs diff --git a/src/samples/EvalClient/EvalDatasetCsvMap.cs b/src/samples/CopilotStudioClient/EvalClient/EvalDatasetCsvMap.cs similarity index 100% rename from src/samples/EvalClient/EvalDatasetCsvMap.cs rename to src/samples/CopilotStudioClient/EvalClient/EvalDatasetCsvMap.cs diff --git a/src/samples/EvalClient/EvalDatasetResultCsvMap.cs b/src/samples/CopilotStudioClient/EvalClient/EvalDatasetResultCsvMap.cs similarity index 100% rename from src/samples/EvalClient/EvalDatasetResultCsvMap.cs rename to src/samples/CopilotStudioClient/EvalClient/EvalDatasetResultCsvMap.cs diff --git a/src/samples/EvalClient/EvaluationService.cs b/src/samples/CopilotStudioClient/EvalClient/EvaluationService.cs similarity index 100% rename from src/samples/EvalClient/EvaluationService.cs rename to src/samples/CopilotStudioClient/EvalClient/EvaluationService.cs diff --git a/src/samples/EvalClient/Program.cs b/src/samples/CopilotStudioClient/EvalClient/Program.cs similarity index 100% rename from src/samples/EvalClient/Program.cs rename to src/samples/CopilotStudioClient/EvalClient/Program.cs diff --git a/src/samples/EvalClient/README.md b/src/samples/CopilotStudioClient/EvalClient/README.md similarity index 100% rename from src/samples/EvalClient/README.md rename to src/samples/CopilotStudioClient/EvalClient/README.md diff --git a/src/samples/EvalClient/appsettings.json b/src/samples/CopilotStudioClient/EvalClient/appsettings.json similarity index 100% rename from src/samples/EvalClient/appsettings.json rename to src/samples/CopilotStudioClient/EvalClient/appsettings.json diff --git a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs b/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs deleted file mode 100644 index 3b652c2b..00000000 --- a/src/samples/CopilotStudioEchoSkill/CopilotStudioBot.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Models; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace CopilotStudioEchoSkill -{ - public class CopilotStudioBot : ActivityHandler - { - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) - { - var messageText = $"(EchoSkill) Ending conversation..."; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); - - // Indicate this conversation is over by sending an EndOfConversation Activity. - // This bot doesn't return a value, but if it did it could be put in Activity.Value. - var endOfConversation = Activity.CreateEndOfConversationActivity(); - endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; - await turnContext.SendActivityAsync(endOfConversation, cancellationToken); - } - else - { - var messageText = $"Echo(EchoSkill): {turnContext.Activity.Text}"; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); - messageText = "Echo(EchoSkill): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent."; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput.ToString()), cancellationToken); - } - } - - protected override Task OnEndOfConversationActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - // This will be called if the root bot is ending the conversation. Sending additional messages should be - // avoided as the conversation may have been deleted. - // Perform cleanup of resources if needed. - return Task.CompletedTask; - } - - protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) - { - // Display a welcome message to the new members. - foreach (var member in membersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Hi, This is EchoSkill"), cancellationToken); - } - } - } - } -} diff --git a/src/samples/CopilotStudioEchoSkill/Program.cs b/src/samples/CopilotStudioEchoSkill/Program.cs index 238e2df3..141bf014 100644 --- a/src/samples/CopilotStudioEchoSkill/Program.cs +++ b/src/samples/CopilotStudioEchoSkill/Program.cs @@ -2,8 +2,12 @@ // Licensed under the MIT License. using CopilotStudioEchoSkill; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -20,8 +24,62 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add basic bot functionality -builder.AddBot(); +// Add bot routes and logic +builder.AddBot(sp => +{ + var options = new ApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; + + var app = new Application(options); + + // Display a welcome message + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hi, This is EchoSkill"), cancellationToken); + } + } + }); + + app.OnActivity(ActivityTypes.EndOfConversation, async (turnContext, turnState, cancellationToken) => + { + // This will be called if the root bot is ending the conversation. Sending additional messages should be + // avoided as the conversation may have been deleted. + // Perform cleanup of resources if needed. + await turnContext.SendActivityAsync("Received EndOfConversation", cancellationToken: cancellationToken); + }); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => + { + if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) + { + var messageText = $"(EchoSkill) Ending conversation..."; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); + + // Indicate this conversation is over by sending an EndOfConversation Activity. + // This bot doesn't return a value, but if it did it could be put in Activity.Value. + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; + await turnContext.SendActivityAsync(endOfConversation, cancellationToken); + } + else + { + var messageText = $"Echo(EchoSkill): {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); + messageText = "Echo(EchoSkill): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent."; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput.ToString()), cancellationToken); + } + }); + + return app; +}); var app = builder.Build(); diff --git a/src/samples/CopilotStudioEchoSkill/README.md b/src/samples/CopilotStudioEchoSkill/README.md index 46dd101b..c8a332ec 100644 --- a/src/samples/CopilotStudioEchoSkill/README.md +++ b/src/samples/CopilotStudioEchoSkill/README.md @@ -64,8 +64,8 @@ This sample is intended to introduce you to: } ``` - 1. Replace `{{ClientId}}` with the AppId of the bot identity. - 1. Replace `{{TenantId}}` with the Tenant Id where your application is registered. + 1. Replace all `{{ClientId}}` with the AppId of the bot identity. + 1. Replace all `{{TenantId}}` with the Tenant Id where your application is registered. 1. Set the **ClientSecret** to the Secret that was created for your identity. > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. diff --git a/src/samples/EchoBot/MyBot.cs b/src/samples/EchoBot/MyBot.cs deleted file mode 100644 index 544247b8..00000000 --- a/src/samples/EchoBot/MyBot.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Compat; -using Microsoft.Agents.Core.Models; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace EchoBot -{ - // This is the core handler for the Bot Message loop. Each new request will be processed by this class. - public class MyBot : ActivityHandler - { - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - // Create a new Activity from the message the user provided and modify the text to echo back. - IActivity message = MessageFactory.Text($"Echo: {turnContext.Activity.Text}"); - - // Send the response message back to the user. - await turnContext.SendActivityAsync(message, cancellationToken); - } - - protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) - { - // When someone (or something) connects to the bot, a MembersAdded activity is received. - // For this sample, we treat this as a welcome event, and send a message saying hello. - // For more details around the membership lifecycle, please see the lifecycle documentation. - IActivity message = MessageFactory.Text("Hello and Welcome!"); - - // Send the response message back to the user. - await turnContext.SendActivityAsync(message, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/samples/EchoBot/Program.cs b/src/samples/EchoBot/Program.cs index 151abaa0..f6f522f7 100644 --- a/src/samples/EchoBot/Program.cs +++ b/src/samples/EchoBot/Program.cs @@ -2,22 +2,68 @@ // Licensed under the MIT License. using EchoBot; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add basic bot functionality -builder.AddBot(); +// Add bot routes and logic +builder.AddBot(sp => +{ + var options = new ApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; + + var app = new Application(options); + + // Display a welcome message + app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + }); + + // Listen for user to say "/reset" and then delete conversation state + app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); + }); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => + { + // Increment count state. + int count = turnState.Conversation.IncrementMessageCount(); + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + }); + + return app; +}); + var app = builder.Build(); diff --git a/src/samples/EchoBot/README.md b/src/samples/EchoBot/README.md index 5fa06e10..922c8aea 100644 --- a/src/samples/EchoBot/README.md +++ b/src/samples/EchoBot/README.md @@ -53,31 +53,29 @@ If you type a message and hit enter, or the send arrow, your messages should be ```json "TokenValidation": { "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" }, "Connections": { - "BotServiceConnection": { - "Assembly": "Microsoft.Agents.Authentication.Msal", - "Type": "MsalAuth", + "BotServiceConnection": { "Settings": { - "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. - "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. - "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. - "Scopes": [ - "https://api.botframework.com/.default" - ], - "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] } - } + } + }, ``` - 1. Set the **ClientId** to the AppId of the bot identity. + 1. Replace all **{{ClientId}}** with the AppId of the bot. + 1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered. 1. Set the **ClientSecret** to the Secret that was created for your identity. - 1. Set the **TenantId** to the Tenant Id where your application is registered. - 1. Set the **Audience** to the AppId of the bot identity. > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. diff --git a/src/samples/Application/messaging.echoBot/StateExtensions.cs b/src/samples/EchoBot/StateExtensions.cs similarity index 100% rename from src/samples/Application/messaging.echoBot/StateExtensions.cs rename to src/samples/EchoBot/StateExtensions.cs diff --git a/src/samples/EchoBot/appsettings.json b/src/samples/EchoBot/appsettings.json index d25215b2..8a57c873 100644 --- a/src/samples/EchoBot/appsettings.json +++ b/src/samples/EchoBot/appsettings.json @@ -1,8 +1,9 @@ { "TokenValidation": { "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" }, "Connections": { @@ -10,7 +11,7 @@ "Settings": { "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. "Scopes": [ "https://api.botframework.com/.default" diff --git a/src/samples/SemanticKernel/WeatherBot/Agents/WeatherForecastAgent.cs b/src/samples/SemanticKernel/WeatherBot/Agents/WeatherForecastAgent.cs index b8f3f6c2..b39e077e 100644 --- a/src/samples/SemanticKernel/WeatherBot/Agents/WeatherForecastAgent.cs +++ b/src/samples/SemanticKernel/WeatherBot/Agents/WeatherForecastAgent.cs @@ -15,7 +15,6 @@ namespace WeatherBot.Agents public class WeatherForecastAgent { private readonly Kernel _kernel; - private readonly ChatHistory _chatHistory; private readonly ChatCompletionAgent _agent; private int retryCount; @@ -40,7 +39,6 @@ public class WeatherForecastAgent public WeatherForecastAgent(Kernel kernel) { this._kernel = kernel; - this._chatHistory = []; // Define the agent this._agent = @@ -67,15 +65,15 @@ public WeatherForecastAgent(Kernel kernel) ///

/// A message to process. /// An instance of - public async Task InvokeAgentAsync(string input) + public async Task InvokeAgentAsync(string input, ChatHistory chatHistory) { ChatMessageContent message = new(AuthorRole.User, input); - this._chatHistory.Add(message); + chatHistory.Add(message); StringBuilder sb = new(); - await foreach (ChatMessageContent response in this._agent.InvokeAsync(this._chatHistory)) + await foreach (ChatMessageContent response in this._agent.InvokeAsync(chatHistory)) { - this._chatHistory.Add(response); + chatHistory.Add(response); sb.Append(response.Content); } @@ -97,7 +95,7 @@ public async Task InvokeAgentAsync(string input) // Try again, providing corrective feedback to the model so that it can correct its mistake this.retryCount++; - return await InvokeAgentAsync($"That response did not match the expected format. Please try again. Error: {je.Message}"); + return await InvokeAgentAsync($"That response did not match the expected format. Please try again. Error: {je.Message}", chatHistory); } } } diff --git a/src/samples/SemanticKernel/WeatherBot/MyBot.cs b/src/samples/SemanticKernel/WeatherBot/MyBot.cs index 58fa06c6..ea4eeeec 100644 --- a/src/samples/SemanticKernel/WeatherBot/MyBot.cs +++ b/src/samples/SemanticKernel/WeatherBot/MyBot.cs @@ -2,9 +2,10 @@ // Licensed under the MIT License. using Microsoft.Agents.BotBuilder; -using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; -using System.Collections.Generic; +using Microsoft.SemanticKernel.ChatCompletion; using System.Threading; using System.Threading.Tasks; using WeatherBot.Agents; @@ -12,19 +13,27 @@ namespace WeatherBot { // This is the core handler for the Bot Message loop. Each new request will be processed by this class. - public class MyBot : ActivityHandler + public class MyBot : Application { private readonly WeatherForecastAgent _weatherAgent; - public MyBot(WeatherForecastAgent weatherAgent) + public MyBot(ApplicationOptions options, WeatherForecastAgent weatherAgent) : base(options) { - this._weatherAgent = weatherAgent; + _weatherAgent = weatherAgent; + + // Setup Activity routes + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, MessageActivityAsync); } - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { + var chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory()); + // Invoke the WeatherForecastAgent to process the message - var forecastResponse = await _weatherAgent.InvokeAgentAsync(turnContext.Activity.Text); + var forecastResponse = await _weatherAgent.InvokeAgentAsync(turnContext.Activity.Text, chatHistory); if (forecastResponse == null) { await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, I couldn't get the weather forecast at the moment."), cancellationToken); @@ -46,15 +55,15 @@ protected override async Task OnMessageActivityAsync(ITurnContext membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { - // When someone (or something) connects to the bot, a MembersAdded activity is received. - // For this sample, we treat this as a welcome event, and send a message saying hello. - // For more details around the membership lifecycle, please see the lifecycle documentation. - IActivity message = MessageFactory.Text("Hello and Welcome! I'm here to help with all your weather forecast needs!"); - - // Send the response message back to the user. - await turnContext.SendActivityAsync(message, cancellationToken); + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome! I'm here to help with all your weather forecast needs!"), cancellationToken); + } + } } } } \ No newline at end of file diff --git a/src/samples/SemanticKernel/WeatherBot/Program.cs b/src/samples/SemanticKernel/WeatherBot/Program.cs index 559026fe..ca625da7 100644 --- a/src/samples/SemanticKernel/WeatherBot/Program.cs +++ b/src/samples/SemanticKernel/WeatherBot/Program.cs @@ -12,6 +12,9 @@ using Azure.Identity; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Storage; +using Microsoft.Agents.BotBuilder.App; var builder = WebApplication.CreateBuilder(args); @@ -53,6 +56,15 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); +builder.Services.AddTransient(sp => +{ + return new ApplicationOptions() + { + StartTypingTimer = true, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; +}); + // Add basic bot functionality builder.AddBot(); diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png b/src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png deleted file mode 100644 index b8cf81afbe2f5bafd8563920edfadb78b7b71be6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3415 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 Bp2+|J diff --git a/src/samples/BotToBot/.gitignore b/src/samples/ToMigrate/BotToBot/.gitignore similarity index 100% rename from src/samples/BotToBot/.gitignore rename to src/samples/ToMigrate/BotToBot/.gitignore diff --git a/src/samples/BotToBot/Bot1/.config/dotnet-tools.json b/src/samples/ToMigrate/BotToBot/Bot1/.config/dotnet-tools.json similarity index 100% rename from src/samples/BotToBot/Bot1/.config/dotnet-tools.json rename to src/samples/ToMigrate/BotToBot/Bot1/.config/dotnet-tools.json diff --git a/src/samples/BotToBot/Bot1/Bot1.csproj b/src/samples/ToMigrate/BotToBot/Bot1/Bot1.csproj similarity index 100% rename from src/samples/BotToBot/Bot1/Bot1.csproj rename to src/samples/ToMigrate/BotToBot/Bot1/Bot1.csproj diff --git a/src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs b/src/samples/ToMigrate/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs similarity index 100% rename from src/samples/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs rename to src/samples/ToMigrate/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs diff --git a/src/samples/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/ToMigrate/BotToBot/Bot1/Bots/Bot1.cs similarity index 100% rename from src/samples/BotToBot/Bot1/Bots/Bot1.cs rename to src/samples/ToMigrate/BotToBot/Bot1/Bots/Bot1.cs diff --git a/src/samples/BotToBot/Bot1/Controllers/Bot2ResponseController.cs b/src/samples/ToMigrate/BotToBot/Bot1/Controllers/Bot2ResponseController.cs similarity index 100% rename from src/samples/BotToBot/Bot1/Controllers/Bot2ResponseController.cs rename to src/samples/ToMigrate/BotToBot/Bot1/Controllers/Bot2ResponseController.cs diff --git a/src/samples/BotToBot/Bot1/Controllers/BotController.cs b/src/samples/ToMigrate/BotToBot/Bot1/Controllers/BotController.cs similarity index 100% rename from src/samples/BotToBot/Bot1/Controllers/BotController.cs rename to src/samples/ToMigrate/BotToBot/Bot1/Controllers/BotController.cs diff --git a/src/samples/BotToBot/Bot1/Program.cs b/src/samples/ToMigrate/BotToBot/Bot1/Program.cs similarity index 100% rename from src/samples/BotToBot/Bot1/Program.cs rename to src/samples/ToMigrate/BotToBot/Bot1/Program.cs diff --git a/src/samples/BotToBot/Bot1/Properties/launchSettings.json b/src/samples/ToMigrate/BotToBot/Bot1/Properties/launchSettings.json similarity index 100% rename from src/samples/BotToBot/Bot1/Properties/launchSettings.json rename to src/samples/ToMigrate/BotToBot/Bot1/Properties/launchSettings.json diff --git a/src/samples/BotToBot/Bot1/README.md b/src/samples/ToMigrate/BotToBot/Bot1/README.md similarity index 100% rename from src/samples/BotToBot/Bot1/README.md rename to src/samples/ToMigrate/BotToBot/Bot1/README.md diff --git a/src/samples/BotToBot/Bot1/appsettings.json b/src/samples/ToMigrate/BotToBot/Bot1/appsettings.json similarity index 100% rename from src/samples/BotToBot/Bot1/appsettings.json rename to src/samples/ToMigrate/BotToBot/Bot1/appsettings.json diff --git a/src/samples/BotToBot/Bot1/wwwroot/default.htm b/src/samples/ToMigrate/BotToBot/Bot1/wwwroot/default.htm similarity index 100% rename from src/samples/BotToBot/Bot1/wwwroot/default.htm rename to src/samples/ToMigrate/BotToBot/Bot1/wwwroot/default.htm diff --git a/src/samples/BotToBot/Bot2/Bot2.csproj b/src/samples/ToMigrate/BotToBot/Bot2/Bot2.csproj similarity index 100% rename from src/samples/BotToBot/Bot2/Bot2.csproj rename to src/samples/ToMigrate/BotToBot/Bot2/Bot2.csproj diff --git a/src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs b/src/samples/ToMigrate/BotToBot/Bot2/BotAdapterWithErrorHandler.cs similarity index 100% rename from src/samples/BotToBot/Bot2/BotAdapterWithErrorHandler.cs rename to src/samples/ToMigrate/BotToBot/Bot2/BotAdapterWithErrorHandler.cs diff --git a/src/samples/BotToBot/Bot2/Bots/Bot2.cs b/src/samples/ToMigrate/BotToBot/Bot2/Bots/Bot2.cs similarity index 100% rename from src/samples/BotToBot/Bot2/Bots/Bot2.cs rename to src/samples/ToMigrate/BotToBot/Bot2/Bots/Bot2.cs diff --git a/src/samples/BotToBot/Bot2/Controllers/BotController.cs b/src/samples/ToMigrate/BotToBot/Bot2/Controllers/BotController.cs similarity index 100% rename from src/samples/BotToBot/Bot2/Controllers/BotController.cs rename to src/samples/ToMigrate/BotToBot/Bot2/Controllers/BotController.cs diff --git a/src/samples/BotToBot/Bot2/Program.cs b/src/samples/ToMigrate/BotToBot/Bot2/Program.cs similarity index 100% rename from src/samples/BotToBot/Bot2/Program.cs rename to src/samples/ToMigrate/BotToBot/Bot2/Program.cs diff --git a/src/samples/BotToBot/Bot2/Properties/launchSettings.json b/src/samples/ToMigrate/BotToBot/Bot2/Properties/launchSettings.json similarity index 100% rename from src/samples/BotToBot/Bot2/Properties/launchSettings.json rename to src/samples/ToMigrate/BotToBot/Bot2/Properties/launchSettings.json diff --git a/src/samples/BotToBot/Bot2/README.md b/src/samples/ToMigrate/BotToBot/Bot2/README.md similarity index 100% rename from src/samples/BotToBot/Bot2/README.md rename to src/samples/ToMigrate/BotToBot/Bot2/README.md diff --git a/src/samples/BotToBot/Bot2/appsettings.json b/src/samples/ToMigrate/BotToBot/Bot2/appsettings.json similarity index 100% rename from src/samples/BotToBot/Bot2/appsettings.json rename to src/samples/ToMigrate/BotToBot/Bot2/appsettings.json diff --git a/src/samples/BotToBot/Bot2/wwwroot/default.htm b/src/samples/ToMigrate/BotToBot/Bot2/wwwroot/default.htm similarity index 100% rename from src/samples/BotToBot/Bot2/wwwroot/default.htm rename to src/samples/ToMigrate/BotToBot/Bot2/wwwroot/default.htm diff --git a/src/samples/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json b/src/samples/ToMigrate/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json similarity index 100% rename from src/samples/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json rename to src/samples/ToMigrate/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json diff --git a/src/samples/BotToBot/README.md b/src/samples/ToMigrate/BotToBot/README.md similarity index 100% rename from src/samples/BotToBot/README.md rename to src/samples/ToMigrate/BotToBot/README.md diff --git a/src/samples/BotToBot/SimpleBotToBot.sln b/src/samples/ToMigrate/BotToBot/SimpleBotToBot.sln similarity index 100% rename from src/samples/BotToBot/SimpleBotToBot.sln rename to src/samples/ToMigrate/BotToBot/SimpleBotToBot.sln diff --git a/src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj b/src/samples/ToMigrate/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj diff --git a/src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs diff --git a/src/samples/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json diff --git a/src/samples/Teams/AdaptiveCardActions/Cards/SuggestedActions.json b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/SuggestedActions.json similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Cards/SuggestedActions.json rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/SuggestedActions.json diff --git a/src/samples/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json diff --git a/src/samples/Teams/AdaptiveCardActions/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Controllers/BotController.cs diff --git a/src/samples/Teams/AdaptiveCardActions/Images/1.Install.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/1.Install.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/1.Install.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/1.Install.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/3.Red.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/3.Red.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/3.Red.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/3.Red.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/4.Green.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/4.Green.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/4.Green.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/4.Green.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/5.Blue.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/5.Blue.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/5.Blue.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/5.Blue.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/6.CardActions.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/6.CardActions.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/6.CardActions.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/6.CardActions.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png diff --git a/src/samples/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif diff --git a/src/samples/Teams/AdaptiveCardActions/Program.cs b/src/samples/ToMigrate/Teams/AdaptiveCardActions/Program.cs similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/Program.cs rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/Program.cs diff --git a/src/samples/Teams/AdaptiveCardActions/README.md b/src/samples/ToMigrate/Teams/AdaptiveCardActions/README.md similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/README.md rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/README.md diff --git a/src/samples/Application/AuthenticationBot/appManifest/color.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/color.png similarity index 100% rename from src/samples/Application/AuthenticationBot/appManifest/color.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/color.png diff --git a/src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json b/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/manifest.json diff --git a/src/samples/Application/AuthenticationBot/appManifest/outline.png b/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/outline.png similarity index 100% rename from src/samples/Application/AuthenticationBot/appManifest/outline.png rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/outline.png diff --git a/src/samples/Teams/AdaptiveCardActions/appsettings.json b/src/samples/ToMigrate/Teams/AdaptiveCardActions/appsettings.json similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/appsettings.json rename to src/samples/ToMigrate/Teams/AdaptiveCardActions/appsettings.json diff --git a/src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/ToMigrate/Teams/ConversationBot/Bots/TeamsConversationBot.cs similarity index 100% rename from src/samples/Teams/ConversationBot/Bots/TeamsConversationBot.cs rename to src/samples/ToMigrate/Teams/ConversationBot/Bots/TeamsConversationBot.cs diff --git a/src/samples/Teams/ConversationBot/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/ConversationBot/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/ConversationBot/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/ConversationBot/Controllers/BotController.cs diff --git a/src/samples/Teams/ConversationBot/ConversationBot.csproj b/src/samples/ToMigrate/Teams/ConversationBot/ConversationBot.csproj similarity index 100% rename from src/samples/Teams/ConversationBot/ConversationBot.csproj rename to src/samples/ToMigrate/Teams/ConversationBot/ConversationBot.csproj diff --git a/src/samples/Teams/ConversationBot/Program.cs b/src/samples/ToMigrate/Teams/ConversationBot/Program.cs similarity index 100% rename from src/samples/Teams/ConversationBot/Program.cs rename to src/samples/ToMigrate/Teams/ConversationBot/Program.cs diff --git a/src/samples/Teams/ConversationBot/README.md b/src/samples/ToMigrate/Teams/ConversationBot/README.md similarity index 100% rename from src/samples/Teams/ConversationBot/README.md rename to src/samples/ToMigrate/Teams/ConversationBot/README.md diff --git a/src/samples/Teams/ConversationBot/Resources/UserMentionCardTemplate.json b/src/samples/ToMigrate/Teams/ConversationBot/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/Teams/ConversationBot/Resources/UserMentionCardTemplate.json rename to src/samples/ToMigrate/Teams/ConversationBot/Resources/UserMentionCardTemplate.json diff --git a/src/samples/Teams/ConversationBot/appManifest/icon-color.png b/src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-color.png similarity index 100% rename from src/samples/Teams/ConversationBot/appManifest/icon-color.png rename to src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-color.png diff --git a/src/samples/Teams/ConversationBot/appManifest/icon-outline.png b/src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-outline.png similarity index 100% rename from src/samples/Teams/ConversationBot/appManifest/icon-outline.png rename to src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-outline.png diff --git a/src/samples/Teams/ConversationBot/appManifest/manifest.json b/src/samples/ToMigrate/Teams/ConversationBot/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/ConversationBot/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/ConversationBot/appManifest/manifest.json diff --git a/src/samples/Teams/ConversationBot/appsettings.json b/src/samples/ToMigrate/Teams/ConversationBot/appsettings.json similarity index 100% rename from src/samples/Teams/ConversationBot/appsettings.json rename to src/samples/ToMigrate/Teams/ConversationBot/appsettings.json diff --git a/src/samples/Teams/LinkUnfurling/AppManifest/icon-color.png b/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-color.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/AppManifest/icon-color.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-color.png diff --git a/src/samples/Teams/LinkUnfurling/AppManifest/icon-outline.png b/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-outline.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/AppManifest/icon-outline.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-outline.png diff --git a/src/samples/Teams/LinkUnfurling/AppManifest/manifest.json b/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/manifest.json similarity index 100% rename from src/samples/Teams/LinkUnfurling/AppManifest/manifest.json rename to src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/manifest.json diff --git a/src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/ToMigrate/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs similarity index 100% rename from src/samples/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs rename to src/samples/ToMigrate/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs diff --git a/src/samples/Teams/LinkUnfurling/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/LinkUnfurling/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/LinkUnfurling/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/LinkUnfurling/Controllers/BotController.cs diff --git a/src/samples/Teams/LinkUnfurling/Images/Add-App.png b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/Add-App.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/Add-App.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/Add-App.png diff --git a/src/samples/Teams/LinkUnfurling/Images/Link-Unfurling.png b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/Link-Unfurling.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/Link-Unfurling.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/Link-Unfurling.png diff --git a/src/samples/Teams/LinkUnfurling/Images/OpenAppIcon.png b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenAppIcon.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/OpenAppIcon.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenAppIcon.png diff --git a/src/samples/Teams/LinkUnfurling/Images/OpenNewMail.png b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenNewMail.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/OpenNewMail.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenNewMail.png diff --git a/src/samples/Teams/LinkUnfurling/Images/SearchInExtension.png b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/SearchInExtension.png similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/SearchInExtension.png rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/SearchInExtension.png diff --git a/src/samples/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif b/src/samples/ToMigrate/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif similarity index 100% rename from src/samples/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif rename to src/samples/ToMigrate/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif diff --git a/src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj b/src/samples/ToMigrate/Teams/LinkUnfurling/LinkUnfurling.csproj similarity index 100% rename from src/samples/Teams/LinkUnfurling/LinkUnfurling.csproj rename to src/samples/ToMigrate/Teams/LinkUnfurling/LinkUnfurling.csproj diff --git a/src/samples/Teams/LinkUnfurling/Program.cs b/src/samples/ToMigrate/Teams/LinkUnfurling/Program.cs similarity index 100% rename from src/samples/Teams/LinkUnfurling/Program.cs rename to src/samples/ToMigrate/Teams/LinkUnfurling/Program.cs diff --git a/src/samples/Teams/LinkUnfurling/README.md b/src/samples/ToMigrate/Teams/LinkUnfurling/README.md similarity index 100% rename from src/samples/Teams/LinkUnfurling/README.md rename to src/samples/ToMigrate/Teams/LinkUnfurling/README.md diff --git a/src/samples/Teams/LinkUnfurling/appsettings.json b/src/samples/ToMigrate/Teams/LinkUnfurling/appsettings.json similarity index 100% rename from src/samples/Teams/LinkUnfurling/appsettings.json rename to src/samples/ToMigrate/Teams/LinkUnfurling/appsettings.json diff --git a/src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs b/src/samples/ToMigrate/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs diff --git a/src/samples/Teams/Meeting-Context-App/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/Meeting-Context-App/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Controllers/BotController.cs diff --git a/src/samples/Teams/Meeting-Context-App/Images/1.setup.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/1.setup.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/1.setup.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/1.setup.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/2.add_to_meeting.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/2.add_to_meeting.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/2.add_to_meeting.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/2.add_to_meeting.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/3.tab_configuration.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/3.tab_configuration.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/3.tab_configuration.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/3.tab_configuration.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/4.tab_context_details.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/4.tab_context_details.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/4.tab_context_details.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/4.tab_context_details.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/AddToChat.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/AddToChat.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/AddToChat.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/AddToChat.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/Meeting-Details.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Meeting-Details.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/Meeting-Details.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Meeting-Details.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/MeetingContext.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/MeetingContext.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/MeetingContext.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/MeetingContext.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/Participant-Details.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Participant-Details.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/Participant-Details.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Participant-Details.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/ParticipantContext.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/ParticipantContext.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/ParticipantContext.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/ParticipantContext.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/Tab-View.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Tab-View.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/Tab-View.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Tab-View.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/meetingTabContext.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meetingTabContext.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/meetingTabContext.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meetingTabContext.png diff --git a/src/samples/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif b/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif diff --git a/src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj b/src/samples/ToMigrate/Teams/Meeting-Context-App/MeetingContextApp.csproj similarity index 100% rename from src/samples/Teams/Meeting-Context-App/MeetingContextApp.csproj rename to src/samples/ToMigrate/Teams/Meeting-Context-App/MeetingContextApp.csproj diff --git a/src/samples/Teams/Meeting-Context-App/Program.cs b/src/samples/ToMigrate/Teams/Meeting-Context-App/Program.cs similarity index 100% rename from src/samples/Teams/Meeting-Context-App/Program.cs rename to src/samples/ToMigrate/Teams/Meeting-Context-App/Program.cs diff --git a/src/samples/Teams/Meeting-Context-App/README.md b/src/samples/ToMigrate/Teams/Meeting-Context-App/README.md similarity index 100% rename from src/samples/Teams/Meeting-Context-App/README.md rename to src/samples/ToMigrate/Teams/Meeting-Context-App/README.md diff --git a/src/samples/Teams/AdaptiveCardActions/appManifest/color.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/color.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/appManifest/color.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/color.png diff --git a/src/samples/Teams/Meeting-Context-App/appPackage/manifest.json b/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/manifest.json similarity index 100% rename from src/samples/Teams/Meeting-Context-App/appPackage/manifest.json rename to src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/manifest.json diff --git a/src/samples/Teams/AdaptiveCardActions/appManifest/outline.png b/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/outline.png similarity index 100% rename from src/samples/Teams/AdaptiveCardActions/appManifest/outline.png rename to src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/outline.png diff --git a/src/samples/Teams/Meeting-Context-App/appsettings.json b/src/samples/ToMigrate/Teams/Meeting-Context-App/appsettings.json similarity index 100% rename from src/samples/Teams/Meeting-Context-App/appsettings.json rename to src/samples/ToMigrate/Teams/Meeting-Context-App/appsettings.json diff --git a/src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs diff --git a/src/samples/Teams/Meetings-Notification/Cards/AgendaCard.json b/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/AgendaCard.json similarity index 100% rename from src/samples/Teams/Meetings-Notification/Cards/AgendaCard.json rename to src/samples/ToMigrate/Teams/Meetings-Notification/Cards/AgendaCard.json diff --git a/src/samples/Teams/Meetings-Notification/Cards/QuestionTemplate.json b/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/QuestionTemplate.json similarity index 100% rename from src/samples/Teams/Meetings-Notification/Cards/QuestionTemplate.json rename to src/samples/ToMigrate/Teams/Meetings-Notification/Cards/QuestionTemplate.json diff --git a/src/samples/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json b/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json similarity index 100% rename from src/samples/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json rename to src/samples/ToMigrate/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json diff --git a/src/samples/Teams/Meetings-Notification/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Controllers/BotController.cs diff --git a/src/samples/Teams/Meetings-Notification/Images/1.Install.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/1.Install.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/1.Install.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/1.Install.png diff --git a/src/samples/Teams/Meetings-Notification/Images/2.Home_Page.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/2.Home_Page.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/2.Home_Page.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/2.Home_Page.png diff --git a/src/samples/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png diff --git a/src/samples/Teams/Meetings-Notification/Images/4.Option_Card.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/4.Option_Card.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/4.Option_Card.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/4.Option_Card.png diff --git a/src/samples/Teams/Meetings-Notification/Images/5.Output_in_Chat.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/5.Output_in_Chat.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/5.Output_in_Chat.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/5.Output_in_Chat.png diff --git a/src/samples/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png diff --git a/src/samples/Teams/Meetings-Notification/Images/7.Popup_Window.png b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/7.Popup_Window.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/7.Popup_Window.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/7.Popup_Window.png diff --git a/src/samples/Teams/Meetings-Notification/Images/MeetingNotification.gif b/src/samples/ToMigrate/Teams/Meetings-Notification/Images/MeetingNotification.gif similarity index 100% rename from src/samples/Teams/Meetings-Notification/Images/MeetingNotification.gif rename to src/samples/ToMigrate/Teams/Meetings-Notification/Images/MeetingNotification.gif diff --git a/src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj b/src/samples/ToMigrate/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj similarity index 100% rename from src/samples/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj rename to src/samples/ToMigrate/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj diff --git a/src/samples/Teams/Meetings-Notification/Models/ActionBase.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/ActionBase.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/ActionBase.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/ActionBase.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/AgendaItem.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/AgendaItem.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/AgendaItem.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/AgendaItem.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/MeetingAgenda.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingAgenda.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/MeetingAgenda.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingAgenda.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/MeetingNotification.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingNotification.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/MeetingNotification.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingNotification.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/ParticipantDetail.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/ParticipantDetail.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/ParticipantDetail.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/ParticipantDetail.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/PushAgendaAction.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/PushAgendaAction.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/PushAgendaAction.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/PushAgendaAction.cs diff --git a/src/samples/Teams/Meetings-Notification/Models/SubmitFeedback.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Models/SubmitFeedback.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Models/SubmitFeedback.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Models/SubmitFeedback.cs diff --git a/src/samples/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml diff --git a/src/samples/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs diff --git a/src/samples/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml diff --git a/src/samples/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs diff --git a/src/samples/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml diff --git a/src/samples/Teams/Meetings-Notification/Pages/_ViewStart.cshtml b/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/_ViewStart.cshtml similarity index 100% rename from src/samples/Teams/Meetings-Notification/Pages/_ViewStart.cshtml rename to src/samples/ToMigrate/Teams/Meetings-Notification/Pages/_ViewStart.cshtml diff --git a/src/samples/Teams/Meetings-Notification/Program.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Program.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Program.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Program.cs diff --git a/src/samples/Teams/Meetings-Notification/README.md b/src/samples/ToMigrate/Teams/Meetings-Notification/README.md similarity index 100% rename from src/samples/Teams/Meetings-Notification/README.md rename to src/samples/ToMigrate/Teams/Meetings-Notification/README.md diff --git a/src/samples/Teams/Meetings-Notification/Titles.cs b/src/samples/ToMigrate/Teams/Meetings-Notification/Titles.cs similarity index 100% rename from src/samples/Teams/Meetings-Notification/Titles.cs rename to src/samples/ToMigrate/Teams/Meetings-Notification/Titles.cs diff --git a/src/samples/Teams/Meeting-Context-App/appPackage/color.png b/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/color.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/appPackage/color.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/color.png diff --git a/src/samples/Teams/Meetings-Notification/appPackage/manifest.json b/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/manifest.json similarity index 100% rename from src/samples/Teams/Meetings-Notification/appPackage/manifest.json rename to src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/manifest.json diff --git a/src/samples/Teams/Meeting-Context-App/appPackage/outline.png b/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/outline.png similarity index 100% rename from src/samples/Teams/Meeting-Context-App/appPackage/outline.png rename to src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/outline.png diff --git a/src/samples/Teams/Meetings-Notification/appsettings.json b/src/samples/ToMigrate/Teams/Meetings-Notification/appsettings.json similarity index 100% rename from src/samples/Teams/Meetings-Notification/appsettings.json rename to src/samples/ToMigrate/Teams/Meetings-Notification/appsettings.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs diff --git a/src/samples/Teams/MessagingExtensionsSearch/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Controllers/BotController.cs diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/1.Install.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/1.Install.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/1.Install.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/1.Install.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/Images/msgext-search.gif b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/msgext-search.gif similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Images/msgext-search.gif rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/msgext-search.gif diff --git a/src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj diff --git a/src/samples/Teams/MessagingExtensionsSearch/Program.cs b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Program.cs similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Program.cs rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Program.cs diff --git a/src/samples/Teams/MessagingExtensionsSearch/README.md b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/README.md similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/README.md rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/README.md diff --git a/src/samples/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/Resources/connectorCard.json b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/connectorCard.json similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/Resources/connectorCard.json rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/connectorCard.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/appManifest/icon-color.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-color.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/appManifest/icon-color.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-color.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png diff --git a/src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/manifest.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/appsettings.json b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appsettings.json similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/appsettings.json rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appsettings.json diff --git a/src/samples/Teams/MessagingExtensionsSearch/wwwroot/default.html b/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/wwwroot/default.html similarity index 100% rename from src/samples/Teams/MessagingExtensionsSearch/wwwroot/default.html rename to src/samples/ToMigrate/Teams/MessagingExtensionsSearch/wwwroot/default.html diff --git a/src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/ToMigrate/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs similarity index 100% rename from src/samples/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs rename to src/samples/ToMigrate/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs diff --git a/src/samples/Teams/TaskModule/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/TaskModule/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/TaskModule/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/TaskModule/Controllers/BotController.cs diff --git a/src/samples/Teams/TaskModule/Helper/AdaptiveCardHelper.cs b/src/samples/ToMigrate/Teams/TaskModule/Helper/AdaptiveCardHelper.cs similarity index 100% rename from src/samples/Teams/TaskModule/Helper/AdaptiveCardHelper.cs rename to src/samples/ToMigrate/Teams/TaskModule/Helper/AdaptiveCardHelper.cs diff --git a/src/samples/Teams/TaskModule/Helper/ApplicationSettings.cs b/src/samples/ToMigrate/Teams/TaskModule/Helper/ApplicationSettings.cs similarity index 100% rename from src/samples/Teams/TaskModule/Helper/ApplicationSettings.cs rename to src/samples/ToMigrate/Teams/TaskModule/Helper/ApplicationSettings.cs diff --git a/src/samples/Teams/TaskModule/Helper/DeeplinkHelper.cs b/src/samples/ToMigrate/Teams/TaskModule/Helper/DeeplinkHelper.cs similarity index 100% rename from src/samples/Teams/TaskModule/Helper/DeeplinkHelper.cs rename to src/samples/ToMigrate/Teams/TaskModule/Helper/DeeplinkHelper.cs diff --git a/src/samples/Teams/TaskModule/Helper/UIConstants.cs b/src/samples/ToMigrate/Teams/TaskModule/Helper/UIConstants.cs similarity index 100% rename from src/samples/Teams/TaskModule/Helper/UIConstants.cs rename to src/samples/ToMigrate/Teams/TaskModule/Helper/UIConstants.cs diff --git a/src/samples/Teams/TaskModule/Images/1.InstallApp.png b/src/samples/ToMigrate/Teams/TaskModule/Images/1.InstallApp.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/1.InstallApp.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/1.InstallApp.png diff --git a/src/samples/Teams/TaskModule/Images/10.GroupChat.png b/src/samples/ToMigrate/Teams/TaskModule/Images/10.GroupChat.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/10.GroupChat.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/10.GroupChat.png diff --git a/src/samples/Teams/TaskModule/Images/11.GroupDialogs.png b/src/samples/ToMigrate/Teams/TaskModule/Images/11.GroupDialogs.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/11.GroupDialogs.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/11.GroupDialogs.png diff --git a/src/samples/Teams/TaskModule/Images/12.GroupCustomForm.png b/src/samples/ToMigrate/Teams/TaskModule/Images/12.GroupCustomForm.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/12.GroupCustomForm.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/12.GroupCustomForm.png diff --git a/src/samples/Teams/TaskModule/Images/13.GroupResults.png b/src/samples/ToMigrate/Teams/TaskModule/Images/13.GroupResults.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/13.GroupResults.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/13.GroupResults.png diff --git a/src/samples/Teams/TaskModule/Images/2.Dialogs.png b/src/samples/ToMigrate/Teams/TaskModule/Images/2.Dialogs.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/2.Dialogs.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/2.Dialogs.png diff --git a/src/samples/Teams/TaskModule/Images/3.AdaptiveCard.png b/src/samples/ToMigrate/Teams/TaskModule/Images/3.AdaptiveCard.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/3.AdaptiveCard.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/3.AdaptiveCard.png diff --git a/src/samples/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png b/src/samples/ToMigrate/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png diff --git a/src/samples/Teams/TaskModule/Images/5.CustomForm.png b/src/samples/ToMigrate/Teams/TaskModule/Images/5.CustomForm.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/5.CustomForm.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/5.CustomForm.png diff --git a/src/samples/Teams/TaskModule/Images/6.Results.png b/src/samples/ToMigrate/Teams/TaskModule/Images/6.Results.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/6.Results.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/6.Results.png diff --git a/src/samples/Teams/TaskModule/Images/7.YouTube.png b/src/samples/ToMigrate/Teams/TaskModule/Images/7.YouTube.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/7.YouTube.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/7.YouTube.png diff --git a/src/samples/Teams/TaskModule/Images/8.Task.png b/src/samples/ToMigrate/Teams/TaskModule/Images/8.Task.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/8.Task.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/8.Task.png diff --git a/src/samples/Teams/TaskModule/Images/9.PowerApp.png b/src/samples/ToMigrate/Teams/TaskModule/Images/9.PowerApp.png similarity index 100% rename from src/samples/Teams/TaskModule/Images/9.PowerApp.png rename to src/samples/ToMigrate/Teams/TaskModule/Images/9.PowerApp.png diff --git a/src/samples/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif b/src/samples/ToMigrate/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif similarity index 100% rename from src/samples/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif rename to src/samples/ToMigrate/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif diff --git a/src/samples/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs diff --git a/src/samples/Teams/TaskModule/Models/CardTaskFetchValue.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/CardTaskFetchValue.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/CardTaskFetchValue.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/CardTaskFetchValue.cs diff --git a/src/samples/Teams/TaskModule/Models/TaskModuleIds.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleIds.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/TaskModuleIds.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleIds.cs diff --git a/src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleResponseFactory.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/TaskModuleResponseFactory.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleResponseFactory.cs diff --git a/src/samples/Teams/TaskModule/Models/TaskModuleUIConstants.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleUIConstants.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/TaskModuleUIConstants.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleUIConstants.cs diff --git a/src/samples/Teams/TaskModule/Models/UISettings.cs b/src/samples/ToMigrate/Teams/TaskModule/Models/UISettings.cs similarity index 100% rename from src/samples/Teams/TaskModule/Models/UISettings.cs rename to src/samples/ToMigrate/Teams/TaskModule/Models/UISettings.cs diff --git a/src/samples/Teams/TaskModule/Pages/Configure.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/Configure.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/Configure.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/Configure.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/CustomForm.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/CustomForm.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/CustomForm.cshtml.cs b/src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml.cs similarity index 100% rename from src/samples/Teams/TaskModule/Pages/CustomForm.cshtml.cs rename to src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml.cs diff --git a/src/samples/Teams/TaskModule/Pages/HelloWorld.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/HelloWorld.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/HelloWorld.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/HelloWorld.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/PowerApp.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/PowerApp.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/PowerApp.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/PowerApp.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/Shared/_Layout.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_Layout.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/Shared/_Layout.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_Layout.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/Tasks.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/Tasks.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/Tasks.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/Tasks.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/YouTube.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/YouTube.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/YouTube.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/YouTube.cshtml diff --git a/src/samples/Teams/TaskModule/Pages/_ViewStart.cshtml b/src/samples/ToMigrate/Teams/TaskModule/Pages/_ViewStart.cshtml similarity index 100% rename from src/samples/Teams/TaskModule/Pages/_ViewStart.cshtml rename to src/samples/ToMigrate/Teams/TaskModule/Pages/_ViewStart.cshtml diff --git a/src/samples/Teams/TaskModule/Program.cs b/src/samples/ToMigrate/Teams/TaskModule/Program.cs similarity index 100% rename from src/samples/Teams/TaskModule/Program.cs rename to src/samples/ToMigrate/Teams/TaskModule/Program.cs diff --git a/src/samples/Teams/TaskModule/README.md b/src/samples/ToMigrate/Teams/TaskModule/README.md similarity index 100% rename from src/samples/Teams/TaskModule/README.md rename to src/samples/ToMigrate/Teams/TaskModule/README.md diff --git a/src/samples/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json b/src/samples/ToMigrate/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json similarity index 100% rename from src/samples/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json rename to src/samples/ToMigrate/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json diff --git a/src/samples/Teams/TaskModule/Resources/adaptiveCard.json b/src/samples/ToMigrate/Teams/TaskModule/Resources/adaptiveCard.json similarity index 100% rename from src/samples/Teams/TaskModule/Resources/adaptiveCard.json rename to src/samples/ToMigrate/Teams/TaskModule/Resources/adaptiveCard.json diff --git a/src/samples/Teams/TaskModule/TaskModule.csproj b/src/samples/ToMigrate/Teams/TaskModule/TaskModule.csproj similarity index 100% rename from src/samples/Teams/TaskModule/TaskModule.csproj rename to src/samples/ToMigrate/Teams/TaskModule/TaskModule.csproj diff --git a/src/samples/Teams/Meetings-Notification/appPackage/color.png b/src/samples/ToMigrate/Teams/TaskModule/appManifest/color.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/appPackage/color.png rename to src/samples/ToMigrate/Teams/TaskModule/appManifest/color.png diff --git a/src/samples/Teams/TaskModule/appManifest/manifest.json b/src/samples/ToMigrate/Teams/TaskModule/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/TaskModule/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/TaskModule/appManifest/manifest.json diff --git a/src/samples/Teams/Meetings-Notification/appPackage/outline.png b/src/samples/ToMigrate/Teams/TaskModule/appManifest/outline.png similarity index 100% rename from src/samples/Teams/Meetings-Notification/appPackage/outline.png rename to src/samples/ToMigrate/Teams/TaskModule/appManifest/outline.png diff --git a/src/samples/Teams/TaskModule/appsettings.json b/src/samples/ToMigrate/Teams/TaskModule/appsettings.json similarity index 100% rename from src/samples/Teams/TaskModule/appsettings.json rename to src/samples/ToMigrate/Teams/TaskModule/appsettings.json diff --git a/src/samples/Teams/TaskModule/assets/sample.json b/src/samples/ToMigrate/Teams/TaskModule/assets/sample.json similarity index 100% rename from src/samples/Teams/TaskModule/assets/sample.json rename to src/samples/ToMigrate/Teams/TaskModule/assets/sample.json diff --git a/src/samples/Teams/TaskModule/wwwroot/css/Site.css b/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/Site.css similarity index 100% rename from src/samples/Teams/TaskModule/wwwroot/css/Site.css rename to src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/Site.css diff --git a/src/samples/Teams/TaskModule/wwwroot/css/custom.css b/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/custom.css similarity index 100% rename from src/samples/Teams/TaskModule/wwwroot/css/custom.css rename to src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/custom.css diff --git a/src/samples/Teams/TaskModule/wwwroot/css/msteams-16.css b/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/msteams-16.css similarity index 100% rename from src/samples/Teams/TaskModule/wwwroot/css/msteams-16.css rename to src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/msteams-16.css diff --git a/src/samples/Teams/TaskModule/wwwroot/default.htm b/src/samples/ToMigrate/Teams/TaskModule/wwwroot/default.htm similarity index 100% rename from src/samples/Teams/TaskModule/wwwroot/default.htm rename to src/samples/ToMigrate/Teams/TaskModule/wwwroot/default.htm diff --git a/src/samples/Teams/bot-all-cards/BotAllCards.csproj b/src/samples/ToMigrate/Teams/bot-all-cards/BotAllCards.csproj similarity index 100% rename from src/samples/Teams/bot-all-cards/BotAllCards.csproj rename to src/samples/ToMigrate/Teams/bot-all-cards/BotAllCards.csproj diff --git a/src/samples/Teams/bot-all-cards/Bots/DialogBot.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Bots/DialogBot.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Bots/DialogBot.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Bots/DialogBot.cs diff --git a/src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Bots/TeamsBot.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Bots/TeamsBot.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Bots/TeamsBot.cs diff --git a/src/samples/Teams/bot-all-cards/Cards/AllCards.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Cards/AllCards.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Cards/AllCards.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Cards/AllCards.cs diff --git a/src/samples/Teams/bot-all-cards/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-all-cards/Dialogs/MainDialog.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Dialogs/MainDialog.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Dialogs/MainDialog.cs diff --git a/src/samples/Teams/bot-all-cards/Images/1.Install.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/1.Install.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/1.Install.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/1.Install.png diff --git a/src/samples/Teams/bot-all-cards/Images/10.CollectionCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/10.CollectionCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/10.CollectionCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/10.CollectionCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/11.ConnectorCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/11.ConnectorCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/11.ConnectorCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/11.ConnectorCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/2.Welcome.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/2.Welcome.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/2.Welcome.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/2.Welcome.png diff --git a/src/samples/Teams/bot-all-cards/Images/3.SelectCards.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/3.SelectCards.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/3.SelectCards.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/3.SelectCards.png diff --git a/src/samples/Teams/bot-all-cards/Images/4.AdaptiveCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/4.AdaptiveCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/4.AdaptiveCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/4.AdaptiveCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/5.HeroCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/5.HeroCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/5.HeroCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/5.HeroCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/6.OathCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/6.OathCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/6.OathCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/6.OathCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/7.SignInCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/7.SignInCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/7.SignInCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/7.SignInCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/8.ThumbnailCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/8.ThumbnailCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/8.ThumbnailCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/8.ThumbnailCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/9.ListCard.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/9.ListCard.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/9.ListCard.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/9.ListCard.png diff --git a/src/samples/Teams/bot-all-cards/Images/AdaptiveCardMedia.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/AdaptiveCardMedia.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia.png diff --git a/src/samples/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png diff --git a/src/samples/Teams/bot-all-cards/Images/Authentication.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/Authentication.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/Authentication.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/Authentication.png diff --git a/src/samples/Teams/bot-all-cards/Images/OauthConnection.png b/src/samples/ToMigrate/Teams/bot-all-cards/Images/OauthConnection.png similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/OauthConnection.png rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/OauthConnection.png diff --git a/src/samples/Teams/bot-all-cards/Images/allBotCardsGif.gif b/src/samples/ToMigrate/Teams/bot-all-cards/Images/allBotCardsGif.gif similarity index 100% rename from src/samples/Teams/bot-all-cards/Images/allBotCardsGif.gif rename to src/samples/ToMigrate/Teams/bot-all-cards/Images/allBotCardsGif.gif diff --git a/src/samples/Teams/bot-all-cards/Program.cs b/src/samples/ToMigrate/Teams/bot-all-cards/Program.cs similarity index 100% rename from src/samples/Teams/bot-all-cards/Program.cs rename to src/samples/ToMigrate/Teams/bot-all-cards/Program.cs diff --git a/src/samples/Teams/bot-all-cards/README.md b/src/samples/ToMigrate/Teams/bot-all-cards/README.md similarity index 100% rename from src/samples/Teams/bot-all-cards/README.md rename to src/samples/ToMigrate/Teams/bot-all-cards/README.md diff --git a/src/samples/Teams/bot-all-cards/Resources/adaptiveCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/adaptiveCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCard.json diff --git a/src/samples/Teams/bot-all-cards/Resources/adaptiveCardMedia.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCardMedia.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/adaptiveCardMedia.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCardMedia.json diff --git a/src/samples/Teams/bot-all-cards/Resources/collectionsCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/collectionsCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/collectionsCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/collectionsCard.json diff --git a/src/samples/Teams/bot-all-cards/Resources/heroCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/heroCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/heroCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/heroCard.json diff --git a/src/samples/Teams/bot-all-cards/Resources/listCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/listCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/listCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/listCard.json diff --git a/src/samples/Teams/bot-all-cards/Resources/o365ConnectorCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/o365ConnectorCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/o365ConnectorCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/o365ConnectorCard.json diff --git a/src/samples/Teams/bot-all-cards/Resources/thumbnailCard.json b/src/samples/ToMigrate/Teams/bot-all-cards/Resources/thumbnailCard.json similarity index 100% rename from src/samples/Teams/bot-all-cards/Resources/thumbnailCard.json rename to src/samples/ToMigrate/Teams/bot-all-cards/Resources/thumbnailCard.json diff --git a/src/samples/Teams/TaskModule/appManifest/color.png b/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/color.png similarity index 100% rename from src/samples/Teams/TaskModule/appManifest/color.png rename to src/samples/ToMigrate/Teams/bot-all-cards/appManifest/color.png diff --git a/src/samples/Teams/bot-all-cards/appManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-all-cards/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-all-cards/appManifest/manifest.json diff --git a/src/samples/Teams/TaskModule/appManifest/outline.png b/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/outline.png similarity index 100% rename from src/samples/Teams/TaskModule/appManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-all-cards/appManifest/outline.png diff --git a/src/samples/Teams/bot-all-cards/appsettings.json b/src/samples/ToMigrate/Teams/bot-all-cards/appsettings.json similarity index 100% rename from src/samples/Teams/bot-all-cards/appsettings.json rename to src/samples/ToMigrate/Teams/bot-all-cards/appsettings.json diff --git a/src/samples/Teams/bot-all-cards/wwwroot/default.htm b/src/samples/ToMigrate/Teams/bot-all-cards/wwwroot/default.htm similarity index 100% rename from src/samples/Teams/bot-all-cards/wwwroot/default.htm rename to src/samples/ToMigrate/Teams/bot-all-cards/wwwroot/default.htm diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Images/1.Install.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/1.Install.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Images/1.Install.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/1.Install.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/Program.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Program.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/Program.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Program.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/README.md b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/README.md similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/README.md rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/README.md diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs diff --git a/src/samples/Teams/bot-all-cards/appManifest/color.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/color.png similarity index 100% rename from src/samples/Teams/bot-all-cards/appManifest/color.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/color.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json diff --git a/src/samples/Teams/bot-all-cards/appManifest/outline.png b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/outline.png similarity index 100% rename from src/samples/Teams/bot-all-cards/appManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/outline.png diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/appsettings.json b/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appsettings.json similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/appsettings.json rename to src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appsettings.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Images/Welcome.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/Welcome.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Images/Welcome.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/Welcome.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Program.cs similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/Program.cs rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Program.cs diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/README.md b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/README.md similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/README.md rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/README.md diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/color.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/color.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/appManifest/color.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/color.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json diff --git a/src/samples/Teams/bot-conversation-sso-quickstart/appManifest/outline.png b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/outline.png similarity index 100% rename from src/samples/Teams/bot-conversation-sso-quickstart/appManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/outline.png diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/appsettings.json b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appsettings.json similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/appsettings.json rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appsettings.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/assets/sample.json b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/assets/sample.json similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/assets/sample.json rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/assets/sample.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm b/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm rename to src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Program.cs similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/Program.cs rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Program.cs diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/README.md b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/README.md similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/README.md rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/README.md diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/color.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/appManifest/color.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json diff --git a/src/samples/Teams/bot-people-picker-adaptive-card/appManifest/outline.png b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png similarity index 100% rename from src/samples/Teams/bot-people-picker-adaptive-card/appManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/appsettings.json b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appsettings.json similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/appsettings.json rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appsettings.json diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/assets/sample.json b/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/assets/sample.json similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/assets/sample.json rename to src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/assets/sample.json diff --git a/src/samples/Teams/bot-request-approval/BotRequestApproval.csproj b/src/samples/ToMigrate/Teams/bot-request-approval/BotRequestApproval.csproj similarity index 100% rename from src/samples/Teams/bot-request-approval/BotRequestApproval.csproj rename to src/samples/ToMigrate/Teams/bot-request-approval/BotRequestApproval.csproj diff --git a/src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs b/src/samples/ToMigrate/Teams/bot-request-approval/Bots/ActivityBot.cs similarity index 100% rename from src/samples/Teams/bot-request-approval/Bots/ActivityBot.cs rename to src/samples/ToMigrate/Teams/bot-request-approval/Bots/ActivityBot.cs diff --git a/src/samples/Teams/bot-request-approval/Cards/ApprovedCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/ApprovedCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/ApprovedCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/ApprovedCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/AssignedToCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/AssignedToCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/AssignedToCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/AssignedToCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/CancelCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/CancelCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/CancelCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/CancelCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/InitialCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/InitialCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/InitialCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/InitialCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/OtherMembersCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/OtherMembersCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/OtherMembersCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/OtherMembersCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/RejectedCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RejectedCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/RejectedCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/RejectedCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/RequestCard.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestCard.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/RequestCard.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestCard.json diff --git a/src/samples/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json b/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json similarity index 100% rename from src/samples/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json rename to src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json diff --git a/src/samples/Teams/bot-request-approval/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-request-approval/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-request-approval/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-request-approval/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-request-approval/Images/ApproveRejectCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/ApproveRejectCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/ApproveRejectCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/ApproveRejectCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/ApprovedRequest.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/ApprovedRequest.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/ApprovedRequest.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/ApprovedRequest.png diff --git a/src/samples/Teams/bot-request-approval/Images/CancelledRequest.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/CancelledRequest.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/CancelledRequest.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/CancelledRequest.png diff --git a/src/samples/Teams/bot-request-approval/Images/CreateTask.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/CreateTask.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/CreateTask.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/CreateTask.png diff --git a/src/samples/Teams/bot-request-approval/Images/EditCancelCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/EditCancelCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/EditCancelCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/EditCancelCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/EditTask.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/EditTask.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/EditTask.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/EditTask.png diff --git a/src/samples/Teams/bot-request-approval/Images/InitialCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/InitialCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/InitialCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/InitialCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/ManagerCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/ManagerCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/ManagerCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/ManagerCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/OtherMemberCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/OtherMemberCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/OtherMemberCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/OtherMemberCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/Preview.gif b/src/samples/ToMigrate/Teams/bot-request-approval/Images/Preview.gif similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/Preview.gif rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/Preview.gif diff --git a/src/samples/Teams/bot-request-approval/Images/RequestCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/RequestCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/RequestCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/RequestCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/StatusCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/StatusCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/StatusCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/StatusCard.png diff --git a/src/samples/Teams/bot-request-approval/Images/UserCard.png b/src/samples/ToMigrate/Teams/bot-request-approval/Images/UserCard.png similarity index 100% rename from src/samples/Teams/bot-request-approval/Images/UserCard.png rename to src/samples/ToMigrate/Teams/bot-request-approval/Images/UserCard.png diff --git a/src/samples/Teams/bot-request-approval/Models/InitialSequentialCard.cs b/src/samples/ToMigrate/Teams/bot-request-approval/Models/InitialSequentialCard.cs similarity index 100% rename from src/samples/Teams/bot-request-approval/Models/InitialSequentialCard.cs rename to src/samples/ToMigrate/Teams/bot-request-approval/Models/InitialSequentialCard.cs diff --git a/src/samples/Teams/bot-request-approval/Models/RequestDetails.cs b/src/samples/ToMigrate/Teams/bot-request-approval/Models/RequestDetails.cs similarity index 100% rename from src/samples/Teams/bot-request-approval/Models/RequestDetails.cs rename to src/samples/ToMigrate/Teams/bot-request-approval/Models/RequestDetails.cs diff --git a/src/samples/Teams/bot-request-approval/Program.cs b/src/samples/ToMigrate/Teams/bot-request-approval/Program.cs similarity index 100% rename from src/samples/Teams/bot-request-approval/Program.cs rename to src/samples/ToMigrate/Teams/bot-request-approval/Program.cs diff --git a/src/samples/Teams/bot-request-approval/README.md b/src/samples/ToMigrate/Teams/bot-request-approval/README.md similarity index 100% rename from src/samples/Teams/bot-request-approval/README.md rename to src/samples/ToMigrate/Teams/bot-request-approval/README.md diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png b/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/color.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png rename to src/samples/ToMigrate/Teams/bot-request-approval/appPackage/color.png diff --git a/src/samples/Teams/bot-request-approval/appPackage/manifest.json b/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/manifest.json similarity index 100% rename from src/samples/Teams/bot-request-approval/appPackage/manifest.json rename to src/samples/ToMigrate/Teams/bot-request-approval/appPackage/manifest.json diff --git a/src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png b/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/outline.png similarity index 100% rename from src/samples/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-request-approval/appPackage/outline.png diff --git a/src/samples/Teams/bot-request-approval/appsettings.json b/src/samples/ToMigrate/Teams/bot-request-approval/appsettings.json similarity index 100% rename from src/samples/Teams/bot-request-approval/appsettings.json rename to src/samples/ToMigrate/Teams/bot-request-approval/appsettings.json diff --git a/src/samples/Teams/bot-request-approval/assets/sample.json b/src/samples/ToMigrate/Teams/bot-request-approval/assets/sample.json similarity index 100% rename from src/samples/Teams/bot-request-approval/assets/sample.json rename to src/samples/ToMigrate/Teams/bot-request-approval/assets/sample.json diff --git a/src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Bots/DialogBot.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Bots/DialogBot.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Bots/DialogBot.cs diff --git a/src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs diff --git a/src/samples/Teams/bot-tag-mention/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs diff --git a/src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Dialogs/MainDialog.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/MainDialog.cs diff --git a/src/samples/Teams/bot-tag-mention/Images/1.AddPersonalScope.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/1.AddPersonalScope.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/1.AddPersonalScope.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/1.AddPersonalScope.png diff --git a/src/samples/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png diff --git a/src/samples/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png diff --git a/src/samples/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png diff --git a/src/samples/Teams/bot-tag-mention/Images/5.MetionedTag-2.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag-2.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/5.MetionedTag-2.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag-2.png diff --git a/src/samples/Teams/bot-tag-mention/Images/5.MetionedTag.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/5.MetionedTag.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag.png diff --git a/src/samples/Teams/bot-tag-mention/Images/6.TagMentionDetails.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/6.TagMentionDetails.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/6.TagMentionDetails.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/6.TagMentionDetails.png diff --git a/src/samples/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png diff --git a/src/samples/Teams/bot-tag-mention/Images/8.WithOutCommand.png b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/8.WithOutCommand.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/8.WithOutCommand.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/8.WithOutCommand.png diff --git a/src/samples/Teams/bot-tag-mention/Images/Tag-mention-bot.gif b/src/samples/ToMigrate/Teams/bot-tag-mention/Images/Tag-mention-bot.gif similarity index 100% rename from src/samples/Teams/bot-tag-mention/Images/Tag-mention-bot.gif rename to src/samples/ToMigrate/Teams/bot-tag-mention/Images/Tag-mention-bot.gif diff --git a/src/samples/Teams/bot-tag-mention/Program.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/Program.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/Program.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/Program.cs diff --git a/src/samples/Teams/bot-tag-mention/README.md b/src/samples/ToMigrate/Teams/bot-tag-mention/README.md similarity index 100% rename from src/samples/Teams/bot-tag-mention/README.md rename to src/samples/ToMigrate/Teams/bot-tag-mention/README.md diff --git a/src/samples/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json b/src/samples/ToMigrate/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json rename to src/samples/ToMigrate/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json diff --git a/src/samples/Teams/bot-tag-mention/SimpleGraphClient.cs b/src/samples/ToMigrate/Teams/bot-tag-mention/SimpleGraphClient.cs similarity index 100% rename from src/samples/Teams/bot-tag-mention/SimpleGraphClient.cs rename to src/samples/ToMigrate/Teams/bot-tag-mention/SimpleGraphClient.cs diff --git a/src/samples/Teams/bot-tag-mention/TagMentionBot.csproj b/src/samples/ToMigrate/Teams/bot-tag-mention/TagMentionBot.csproj similarity index 100% rename from src/samples/Teams/bot-tag-mention/TagMentionBot.csproj rename to src/samples/ToMigrate/Teams/bot-tag-mention/TagMentionBot.csproj diff --git a/src/samples/Teams/bot-tag-mention/appManifest/icon-color.png b/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-color.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/appManifest/icon-color.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-color.png diff --git a/src/samples/Teams/bot-tag-mention/appManifest/icon-outline.png b/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-outline.png similarity index 100% rename from src/samples/Teams/bot-tag-mention/appManifest/icon-outline.png rename to src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-outline.png diff --git a/src/samples/Teams/bot-tag-mention/appManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-tag-mention/appManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/manifest.json diff --git a/src/samples/Teams/bot-tag-mention/appsettings.json b/src/samples/ToMigrate/Teams/bot-tag-mention/appsettings.json similarity index 100% rename from src/samples/Teams/bot-tag-mention/appsettings.json rename to src/samples/ToMigrate/Teams/bot-tag-mention/appsettings.json diff --git a/src/samples/Teams/bot-request-approval/appPackage/color.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/color.png similarity index 100% rename from src/samples/Teams/bot-request-approval/appPackage/color.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/color.png diff --git a/src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json b/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/manifest.json similarity index 100% rename from src/samples/Teams/bot-teams-authentication/AppManifest/manifest.json rename to src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/manifest.json diff --git a/src/samples/Teams/bot-request-approval/appPackage/outline.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/outline.png similarity index 100% rename from src/samples/Teams/bot-request-approval/appPackage/outline.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/outline.png diff --git a/src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/DialogBot.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Bots/DialogBot.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/DialogBot.cs diff --git a/src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/TeamsBot.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Bots/TeamsBot.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/TeamsBot.cs diff --git a/src/samples/Teams/bot-teams-authentication/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs diff --git a/src/samples/Teams/bot-teams-authentication/Dialogs/MainDialog.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Dialogs/MainDialog.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/MainDialog.cs diff --git a/src/samples/Teams/bot-teams-authentication/Images/1.Install.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/1.Install.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/1.Install.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/1.Install.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/2.Welcome.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/2.Welcome.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/2.Welcome.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/2.Welcome.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/3.AuthSuccess.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/3.AuthSuccess.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/3.AuthSuccess.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/3.AuthSuccess.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/4.AuthToken.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/4.AuthToken.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/4.AuthToken.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/4.AuthToken.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/5.Logout.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/5.Logout.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/5.Logout.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/5.Logout.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/auth-consent.png b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/auth-consent.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/auth-consent.png rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/auth-consent.png diff --git a/src/samples/Teams/bot-teams-authentication/Images/bot-teams-auth.gif b/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/bot-teams-auth.gif similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Images/bot-teams-auth.gif rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Images/bot-teams-auth.gif diff --git a/src/samples/Teams/bot-teams-authentication/Program.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/Program.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/Program.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/Program.cs diff --git a/src/samples/Teams/bot-teams-authentication/README.md b/src/samples/ToMigrate/Teams/bot-teams-authentication/README.md similarity index 100% rename from src/samples/Teams/bot-teams-authentication/README.md rename to src/samples/ToMigrate/Teams/bot-teams-authentication/README.md diff --git a/src/samples/Teams/bot-teams-authentication/SimpleGraphClient.cs b/src/samples/ToMigrate/Teams/bot-teams-authentication/SimpleGraphClient.cs similarity index 100% rename from src/samples/Teams/bot-teams-authentication/SimpleGraphClient.cs rename to src/samples/ToMigrate/Teams/bot-teams-authentication/SimpleGraphClient.cs diff --git a/src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj b/src/samples/ToMigrate/Teams/bot-teams-authentication/TeamsAuth.csproj similarity index 100% rename from src/samples/Teams/bot-teams-authentication/TeamsAuth.csproj rename to src/samples/ToMigrate/Teams/bot-teams-authentication/TeamsAuth.csproj diff --git a/src/samples/Teams/bot-teams-authentication/appsettings.json b/src/samples/ToMigrate/Teams/bot-teams-authentication/appsettings.json similarity index 100% rename from src/samples/Teams/bot-teams-authentication/appsettings.json rename to src/samples/ToMigrate/Teams/bot-teams-authentication/appsettings.json diff --git a/src/samples/Teams/bot-teams-authentication/assets/sample.json b/src/samples/ToMigrate/Teams/bot-teams-authentication/assets/sample.json similarity index 100% rename from src/samples/Teams/bot-teams-authentication/assets/sample.json rename to src/samples/ToMigrate/Teams/bot-teams-authentication/assets/sample.json diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/Program.cs b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Program.cs similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/Program.cs rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Program.cs diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/README.md b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/README.md similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/README.md rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/README.md diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj diff --git a/src/samples/Teams/bot-teams-authentication/AppManifest/color.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/AppManifest/color.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json diff --git a/src/samples/Teams/bot-teams-authentication/AppManifest/outline.png b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png similarity index 100% rename from src/samples/Teams/bot-teams-authentication/AppManifest/outline.png rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json diff --git a/src/samples/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json b/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json similarity index 100% rename from src/samples/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json rename to src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json diff --git a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs index e7afcf6d..02d3b1cb 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Dialogs.Tests/OAuthPromptTests.cs @@ -699,7 +699,7 @@ public async Task OAuthPromptRecognizeTokenAsync_WithNullTextMessageActivity_Doe var results = await dc.ContinueDialogAsync(cancellationToken); if (results.Status == DialogTurnStatus.Empty) { - await dc.PromptAsync("OAuthPrompt", new PromptOptions() { RetryPrompt = MessageFactory.Text(retryPromptText) }, cancellationToken: cancellationToken); + await dc.PromptAsync("OAuthPrompt", new PromptOptions() { RetryPrompt = MessageFactory.Text(retryPromptText) }, cancellationToken: cancellationToken); } }; From 6e4ffb2ae37be4462f0f64cfb5a05eb87fe65a0a Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 26 Feb 2025 09:22:36 -0600 Subject: [PATCH 50/60] Featurized NormalizeMentionsMiddleware --- .../App/Application.cs | 18 ++-- .../App/ApplicationOptions.cs | 6 ++ .../Compat/NormalizeMentionsMiddleware.cs | 85 +----------------- .../EntityExtension.cs | 87 +++++++++++++++++-- 4 files changed, 100 insertions(+), 96 deletions(-) rename src/libraries/Core/Microsoft.Agents.Core/{Serialization => Models}/EntityExtension.cs (50%) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 864dd707..79572a39 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -24,7 +24,6 @@ public class Application : IBot private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; - // TODO: These really aren't queues, so why this type? private readonly ConcurrentQueue _invokeRoutes; private readonly ConcurrentQueue _routes; private readonly ConcurrentQueue _beforeTurn; @@ -527,13 +526,13 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel ArgumentNullException.ThrowIfNull(turnContext); ArgumentNullException.ThrowIfNull(turnContext.Activity); - await _OnTurnAsync(turnContext, cancellationToken); + await InnerOnTurnAsync(turnContext, cancellationToken); } /// /// Internal method to wrap the logic of handling a bot turn. /// - private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) + private async Task InnerOnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) { try { @@ -544,11 +543,16 @@ private async Task _OnTurnAsync(ITurnContext turnContext, CancellationToken canc }; // Remove @mentions - if (Options.RemoveRecipientMention && ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) + if (ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { - // TODO: What about normalizing mentions (via integrated NormalizeMentions)? - - turnContext.Activity.Text = turnContext.Activity.RemoveRecipientMention(); + if (Options.NormalizeMentions) + { + turnContext.Activity.NormalizeMentions(Options.RemoveRecipientMention); + } + else if (Options.RemoveRecipientMention) + { + turnContext.Activity.Text = turnContext.Activity.RemoveRecipientMention(); + } } // Load turn state diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs index 118ab9a6..3c64c1a1 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs @@ -45,6 +45,12 @@ public class ApplicationOptions ///
public bool RemoveRecipientMention { get; set; } = true; + /// + /// Optional. If true, the bot will automatically normalize mentions across channels. + /// Defaults to true. + /// + public bool NormalizeMentions { get; set; } = true; + /// /// Optional. If true, the bot will automatically start a typing timer when messages are received. /// This allows the bot to automatically indicate that it's received the message and is processing diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs index 3ad50454..270884cf 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/NormalizeMentionsMiddleware.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. using Microsoft.Agents.Core.Models; -using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,89 +40,8 @@ public NormalizeMentionsMiddleware() /// A representing the asynchronous operation. public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) { - NormalizeActivity(turnContext.Activity); + turnContext.Activity.NormalizeMentions(RemoveRecipientMention); await next(cancellationToken).ConfigureAwait(false); } - - /// - /// Normalize the activity. - /// - /// activity. - private void NormalizeActivity(IActivity activity) - { - if (activity.Type == ActivityTypes.Message) - { - if (RemoveRecipientMention) - { - // strip recipient mention tags and text. - activity.RemoveRecipientMention(); - - if (activity.Entities != null) - { - // strip entity.mention records for recipient id. - activity.Entities = activity.Entities.Where(entity => entity is Mention mention && - mention.Mentioned.Id != activity.Recipient.Id).ToList(); - } - } - - // remove tags keeping the inner text. - activity.Text = RemoveAt(activity.Text); - - if (activity.Entities != null) - { - // remove tags from mention records keeping the inner text. - foreach (var entity in activity.GetMentions()) - { - entity.Text = RemoveAt(entity.Text)?.Trim(); - } - } - } - } - - private string RemoveAt(string text) - { - if (string.IsNullOrEmpty(text)) - { - return text; - } - - bool foundTag; - do - { - foundTag = false; - int iAtStart = text.IndexOf("= 0) - { - int iAtEnd = text.IndexOf(">", iAtStart, StringComparison.InvariantCultureIgnoreCase); - if (iAtEnd > 0) - { - int iAtClose = text.IndexOf("", iAtEnd, StringComparison.InvariantCultureIgnoreCase); - if (iAtClose > 0) - { - // replace - var followingText = text.Substring(iAtClose + 5); - - // if first char of followingText is not whitespace - if (!char.IsWhiteSpace(followingText.FirstOrDefault())) - { - // insert space because teams does => Tomis cool => Tomis cool - followingText = $" {followingText}"; - } - - text = text.Substring(0, iAtClose) + followingText; - - // replace - text = text.Substring(0, iAtStart) + text.Substring(iAtEnd + 1); - - // we found one, try again, there may be more. - foundTag = true; - } - } - } - } - while (foundTag); - - return text; - } } } diff --git a/src/libraries/Core/Microsoft.Agents.Core/Serialization/EntityExtension.cs b/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs similarity index 50% rename from src/libraries/Core/Microsoft.Agents.Core/Serialization/EntityExtension.cs rename to src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs index 968c78f0..c2426186 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Serialization/EntityExtension.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs @@ -3,10 +3,10 @@ using System.Text.RegularExpressions; using System.Linq; -using Microsoft.Agents.Core.Models; using System; +using Microsoft.Agents.Core.Serialization; -namespace Microsoft.Agents.Core.Serialization +namespace Microsoft.Agents.Core.Models { public static class EntityExtension { @@ -32,11 +32,88 @@ public static void SetAs(this Entity entity, T obj) entity.Type = copy.Type; entity.Properties = copy.Properties; } - + + public static void NormalizeMentions(this IActivity activity, bool removeMention) + { + if (activity.Type == ActivityTypes.Message) + { + if (removeMention) + { + // strip recipient mention tags and text. + activity.RemoveRecipientMention(); + + if (activity.Entities != null) + { + // strip entity.mention records for recipient id. + activity.Entities = activity.Entities.Where(entity => entity is Mention mention && + mention.Mentioned.Id != activity.Recipient.Id).ToList(); + } + } + + // remove tags keeping the inner text. + activity.Text = RemoveAt(activity.Text); + + if (activity.Entities != null) + { + // remove tags from mention records keeping the inner text. + foreach (var entity in activity.GetMentions()) + { + entity.Text = RemoveAt(entity.Text)?.Trim(); + } + } + } + } + + private static string RemoveAt(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + bool foundTag; + do + { + foundTag = false; + int iAtStart = text.IndexOf("= 0) + { + int iAtEnd = text.IndexOf(">", iAtStart, StringComparison.InvariantCultureIgnoreCase); + if (iAtEnd > 0) + { + int iAtClose = text.IndexOf("", iAtEnd, StringComparison.InvariantCultureIgnoreCase); + if (iAtClose > 0) + { + // replace + var followingText = text.Substring(iAtClose + 5); + + // if first char of followingText is not whitespace + if (!char.IsWhiteSpace(followingText.FirstOrDefault())) + { + // insert space because teams does => Tomis cool => Tomis cool + followingText = $" {followingText}"; + } + + text = text.Substring(0, iAtClose) + followingText; + + // replace + text = text.Substring(0, iAtStart) + text.Substring(iAtEnd + 1); + + // we found one, try again, there may be more. + foundTag = true; + } + } + } + } + while (foundTag); + + return text; + } + /// /// Remove any mention text for given id from the Activity.Text property. For example, given the message - /// @echoBot Hi Bot, this will remove "@echoBot", leaving "Hi Bot". + /// `@echoBot Hi Bot`, this will remove "@echoBot", leaving `Hi Bot`. /// /// /// @@ -76,7 +153,7 @@ public static bool IsStreamingMessage(this IActivity activity) public static StreamInfo GetStreamingEntity(this IActivity activity) { - if (activity.Entities == null || activity.Entities.Count == 0) + if (activity.Entities == null || activity.Entities.Count == 0) { return null; } From 9a69fd4c6c594f47c32fa83d9bb0c5772c8cc3f2 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 26 Feb 2025 09:25:35 -0600 Subject: [PATCH 51/60] Collapsed _OnTurnAsync into OnTurnAsync --- .../Microsoft.Agents.BotBuilder/App/Application.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index 79572a39..bb833d91 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -526,14 +526,6 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel ArgumentNullException.ThrowIfNull(turnContext); ArgumentNullException.ThrowIfNull(turnContext.Activity); - await InnerOnTurnAsync(turnContext, cancellationToken); - } - - /// - /// Internal method to wrap the logic of handling a bot turn. - /// - private async Task InnerOnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { try { // Start typing timer if configured @@ -542,7 +534,7 @@ private async Task InnerOnTurnAsync(ITurnContext turnContext, CancellationToken StartTypingTimer(turnContext); }; - // Remove @mentions + // Handle @mentions if (ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { if (Options.NormalizeMentions) From 771cb10c7839da8467d5d663959d310f3816331c Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 26 Feb 2025 09:52:53 -0600 Subject: [PATCH 52/60] Using ErrorHelper for Application exceptions --- .../App/Application.cs | 19 ++++++++++++------- .../Errors/ErrorHelper.cs | 1 + .../Properties/Resources.Designer.cs | 9 +++++++++ .../Properties/Resources.resx | 6 +++++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs index bb833d91..f6761e59 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs @@ -3,6 +3,7 @@ using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.App.UserAuth; +using Microsoft.Agents.BotBuilder.Errors; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.Core.Models; using System; @@ -20,7 +21,6 @@ namespace Microsoft.Agents.BotBuilder.App public class Application : IBot { private readonly UserAuthenticationFeature _authentication; - private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; @@ -42,7 +42,7 @@ public Application(ApplicationOptions options) if (Options.TurnStateFactory == null) { - // This defaults to a TurnState with TempState + // This defaults to a TurnState with TempState only Options.TurnStateFactory = () => new TurnState(); } @@ -61,11 +61,18 @@ public Application(ApplicationOptions options) } } + #region Application Features + /// /// Fluent interface for accessing Adaptive Card specific features. /// public AdaptiveCardsFeature AdaptiveCards { get; } + /// + /// The application's configured options. + /// + public ApplicationOptions Options { get; } + /// /// Accessing authentication specific features. /// @@ -75,19 +82,17 @@ public UserAuthenticationFeature Authentication { if (_authentication == null) { - throw new InvalidOperationException("The Application.UserAuthentication property is unavailable because no authentication options were configured."); + throw Core.Errors.ExceptionHelper.GenerateException(ErrorHelper.UserAuthenticationNotConfigured, null); } return _authentication; } } - /// - /// The application's configured options. - /// - public ApplicationOptions Options { get; } + #endregion #region Route Handling + /// /// Adds a new route to the application. /// diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs index e3a7dd98..7617d5e9 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs @@ -35,6 +35,7 @@ internal static partial class ErrorHelper internal static AgentErrorDefinition NullIAccessTokenProvider = new AgentErrorDefinition(baseBotBuilderErrorCode, Properties.Resources.IAccessTokenProviderNotFound, "https://aka.ms/AgentsSDK-Error01"); internal static AgentErrorDefinition NullUserTokenProviderIAccessTokenProvider = new AgentErrorDefinition(baseBotBuilderErrorCode-1, Properties.Resources.IAccessTokenProviderNotFound, "https://aka.ms/AgentsSDK-Error01"); + internal static AgentErrorDefinition UserAuthenticationNotConfigured = new AgentErrorDefinition(baseBotBuilderErrorCode - 2, Properties.Resources.UserAuthenticationNotConfigured, "https://aka.ms/AgentsSDK-Error01"); } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs index 0adc72eb..aeead5ed 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs @@ -68,5 +68,14 @@ internal static string IAccessTokenProviderNotFound { return ResourceManager.GetString("IAccessTokenProviderNotFound", resourceCulture); } } + + /// + /// Looks up a localized string similar to The Application.Authentication property is unavailable because no user authentication handlers were configured.. + /// + internal static string UserAuthenticationNotConfigured { + get { + return ResourceManager.GetString("UserAuthenticationNotConfigured", resourceCulture); + } + } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx index dda36bfd..0246ee3a 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx @@ -119,6 +119,10 @@ An instance of IAccessTokenProvider not found for {0} - Raised when an the call get an access token provider returns null. + Raised when the call get an access token provider returns null. + + + The Application.Authentication property is unavailable because no user authentication handlers were configured. + Raised when accessing Application.Authentication but it hasn't been configured. \ No newline at end of file From 335bd8dc9a261dd111c53e1cd4a304d0377cb6ed Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 27 Feb 2025 12:10:18 -0600 Subject: [PATCH 53/60] Added Route rank for ordering, renamed Application, Route Attribute POC. --- src/Microsoft.Agents.SDK.sln | 7 + .../Prompts/OAuthPrompt.cs | 2 +- .../App/ActivityRouteAttribute.cs | 57 +++++++ .../App/AdaptiveCards/AdaptiveCardsFeature.cs | 30 ++-- .../{Application.cs => AgentApplication.cs} | 158 ++++++++++++------ ...nBuilder.cs => AgentApplicationBuilder.cs} | 40 +++-- ...nOptions.cs => AgentApplicationOptions.cs} | 4 +- .../App/ConversationUpdateRouteAttribute.cs | 24 +++ .../App/IRouteAttribute.cs | 12 ++ .../App/RouteList.cs | 49 ++++++ .../App/RouteRank.cs | 12 ++ .../App/UserAuth/UserAuthenticationFeature.cs | 6 +- .../Compat/ShowTypingMiddleware.cs | 1 - .../Errors/ErrorHelper.cs | 4 + .../Properties/Resources.Designer.cs | 20 ++- .../Properties/Resources.resx | 12 +- .../UserAuth/IUserAuthentication.cs | 6 +- .../Errors/AgentErrorDefinition.cs | 5 +- .../Models/EntityExtension.cs | 2 + .../Models/IActivityExtensions.cs | 2 +- .../App/Meetings/MeetingsFeature.cs | 20 +-- .../MessageExtensionsFeature.cs | 56 +++---- .../App/TaskModules/TaskModulesFeature.cs | 16 +- .../App/TeamsApplication.cs | 37 ++-- .../App/TeamsApplicationOptions.cs | 2 +- .../AspNetCore/ChannelApiController.cs | 4 +- src/samples/AuthenticationBot/AuthBot.cs | 78 +++++++++ src/samples/AuthenticationBot/Program.cs | 76 ++------- src/samples/AuthenticationBot/README.md | 17 +- .../AuthenticationBot/appsettings.json | 7 +- .../AdapterWithErrorHandler.cs | 50 ++++++ .../AuthenticationBotCompat.csproj | 34 ++++ .../AuthenticationBotCompat/Bots/AuthBot.cs | 43 +++++ .../AuthenticationBotCompat/Bots/DialogBot.cs | 54 ++++++ .../Controllers/BotController.cs | 25 +++ .../Dialogs/LogoutDialog.cs | 64 +++++++ .../Dialogs/MainDialog.cs | 98 +++++++++++ .../Compat/AuthenticationBotCompat/Program.cs | 62 +++++++ .../Compat/AuthenticationBotCompat/README.md | 100 +++++++++++ .../appManifest/color.png | Bin 0 -> 3415 bytes .../appManifest/manifest.json | 48 ++++++ .../appManifest/outline.png | Bin 0 -> 407 bytes .../AuthenticationBotCompat/appsettings.json | 38 +++++ src/samples/CopilotStudioEchoSkill/MyBot.cs | 65 +++++++ src/samples/CopilotStudioEchoSkill/Program.cs | 57 +------ src/samples/CopilotStudioEchoSkill/README.md | 2 +- src/samples/EchoBot/MyBot.cs | 49 ++++++ src/samples/EchoBot/Program.cs | 42 +---- .../SemanticKernel/WeatherBot/MyBot.cs | 12 +- .../SemanticKernel/WeatherBot/Program.cs | 6 +- .../App/ActivityRouteAttributeTests.cs | 140 ++++++++++++++++ .../App/AdaptiveCardsTests.cs | 18 +- .../App/ApplicationRouteTests.cs | 125 +++++++++++--- .../App/Command.cs | 6 +- .../App/TestUtils/TestApplication.cs | 4 +- .../TestUserAuthenticationFeature.cs | 2 +- .../App/UserAuthenticationFeatureTests.cs | 4 +- .../App/ApplicationRouteTests.cs | 22 +-- 58 files changed, 1546 insertions(+), 390 deletions(-) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{Application.cs => AgentApplication.cs} (76%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{ApplicationBuilder.cs => AgentApplicationBuilder.cs} (67%) rename src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/{ApplicationOptions.cs => AgentApplicationOptions.cs} (95%) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IRouteAttribute.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteList.cs create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteRank.cs create mode 100644 src/samples/AuthenticationBot/AuthBot.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/AdapterWithErrorHandler.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/AuthenticationBotCompat.csproj create mode 100644 src/samples/Compat/AuthenticationBotCompat/Bots/AuthBot.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/Bots/DialogBot.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/Controllers/BotController.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/Dialogs/LogoutDialog.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/Dialogs/MainDialog.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/Program.cs create mode 100644 src/samples/Compat/AuthenticationBotCompat/README.md create mode 100644 src/samples/Compat/AuthenticationBotCompat/appManifest/color.png create mode 100644 src/samples/Compat/AuthenticationBotCompat/appManifest/manifest.json create mode 100644 src/samples/Compat/AuthenticationBotCompat/appManifest/outline.png create mode 100644 src/samples/Compat/AuthenticationBotCompat/appsettings.json create mode 100644 src/samples/CopilotStudioEchoSkill/MyBot.cs create mode 100644 src/samples/EchoBot/MyBot.cs create mode 100644 src/tests/Microsoft.Agents.BotBuilder.Tests/App/ActivityRouteAttributeTests.cs diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index e11818d9..2a56784e 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -110,6 +110,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopilotStudioClient", "samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvalClient", "samples\CopilotStudioClient\EvalClient\EvalClient.csproj", "{A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBotCompat", "samples\Compat\AuthenticationBotCompat\AuthenticationBotCompat.csproj", "{B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -268,6 +270,10 @@ Global {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C}.Release|Any CPU.Build.0 = Release|Any CPU + {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -322,6 +328,7 @@ Global {36494671-1A2D-47F9-B53D-354E0690DA82} = {674A812C-7287-4883-97F9-697D83750648} {CA58CF4C-2D7E-49CE-974C-9939321B1612} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} + {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65} = {36494671-1A2D-47F9-B53D-354E0690DA82} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs index c568ec69..34edaca6 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder.Dialogs/Prompts/OAuthPrompt.cs @@ -188,7 +188,7 @@ public override async Task ContinueDialogAsync(DialogContext d var callerInfo = (CallerInfo)dc.ActiveDialog.State[PersistedCaller]; if (callerInfo != null) { - // set the ServiceUrl to the skill host's Url + // set the ServiceUrl to the caller host's Url dc.Context.Activity.ServiceUrl = callerInfo.CallerServiceUrl; // recreate a ConnectorClient and set it in TurnState so replies use the correct one diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs new file mode 100644 index 00000000..c89f1c13 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Errors; +using System; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Microsoft.Agents.BotBuilder.App +{ + /// + /// Adds an AgentApplication.OnActivity route. + /// Only one of Type, Regex, or Selector will be used: + /// 1. Type + /// 2. Regex + /// 3. Selector + /// + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public class ActivityRouteAttribute : Attribute, IRouteAttribute + { + public string Type { get; set; } + + public string Regex { get; set; } + + public string Selector { get; set; } + + public ushort Rank { get; set; } = RouteRank.Unspecified; + + public void AddRoute(AgentApplication app, MethodInfo method) + { + if (!string.IsNullOrWhiteSpace(Type)) + { + app.OnActivity(Type, method.CreateDelegate(app), rank: Rank ); + } + else if (!string.IsNullOrWhiteSpace(Regex)) + { + app.OnActivity(new Regex(Regex), method.CreateDelegate(app), rank: Rank); + } + else if (!string.IsNullOrWhiteSpace(Selector)) + { + var selectorMethod = app.GetType().GetMethod(Selector, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + ?? throw Core.Errors.ExceptionHelper.GenerateException(ErrorHelper.AttributeSelectorNotFound, null); + + try + { + var delegateSelector = selectorMethod.CreateDelegate(app); + var delegateHandler = method.CreateDelegate(app); + app.OnActivity(delegateSelector, delegateHandler, rank: Rank); + } + catch (ArgumentException ex) + { + throw Core.Errors.ExceptionHelper.GenerateException(ErrorHelper.AttributeSelectorInvalid, ex); + } + } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs index 24969d4e..1d1243ba 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AdaptiveCards/AdaptiveCardsFeature.cs @@ -31,13 +31,13 @@ public class AdaptiveCardsFeature private static readonly string SEARCH_INVOKE_NAME = "application/search"; private static readonly string DEFAULT_ACTION_SUBMIT_FILTER = "verb"; - private readonly Application _app; + private readonly AgentApplication _app; /// /// Creates a new instance of the AdaptiveCards class. /// /// The top level application class to register handlers with. - public AdaptiveCardsFeature(Application app) + public AdaptiveCardsFeature(AgentApplication app) { this._app = app; } @@ -48,7 +48,7 @@ public AdaptiveCardsFeature(Application app) /// The named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handler) + public AgentApplication OnActionExecute(string verb, ActionExecuteHandlerAsync handler) { ArgumentException.ThrowIfNullOrWhiteSpace(verb); ArgumentNullException.ThrowIfNull(handler); @@ -62,7 +62,7 @@ public Application OnActionExecute(string verb, ActionExecuteHandlerAsync handle /// Regular expression to match against the named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) + public AgentApplication OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -76,7 +76,7 @@ public Application OnActionExecute(Regex verbPattern, ActionExecuteHandlerAsync /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) + public AgentApplication OnActionExecute(RouteSelectorAsync routeSelector, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -106,7 +106,7 @@ public Application OnActionExecute(RouteSelectorAsync routeSelector, ActionExecu /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) + public AgentApplication OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -157,7 +157,7 @@ public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionE /// The named action to be handled. /// Function to call when the action is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(string verb, ActionSubmitHandler handler) + public AgentApplication OnActionSubmit(string verb, ActionSubmitHandler handler) { ArgumentException.ThrowIfNullOrWhiteSpace(verb); ArgumentNullException.ThrowIfNull(handler); @@ -189,7 +189,7 @@ public Application OnActionSubmit(string verb, ActionSubmitHandler handler) /// Regular expression to match against the named action to be handled. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) + public AgentApplication OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -221,7 +221,7 @@ public Application OnActionSubmit(Regex verbPattern, ActionSubmitHandler handler /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) + public AgentApplication OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -236,7 +236,7 @@ public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmit await handler(turnContext, turnState, turnContext.Activity.Value, cancellationToken); }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); return _app; } @@ -263,7 +263,7 @@ public Application OnActionSubmit(RouteSelectorAsync routeSelector, ActionSubmit /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) + public AgentApplication OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSubmitHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -297,7 +297,7 @@ public Application OnActionSubmit(MultipleRouteSelector routeSelectors, ActionSu /// The dataset to be searched. /// Function to call when the search is triggered. /// The application instance for chaining purposes. - public Application OnSearch(string dataset, SearchHandlerAsync handler) + public AgentApplication OnSearch(string dataset, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(dataset); ArgumentNullException.ThrowIfNull(handler); @@ -311,7 +311,7 @@ public Application OnSearch(string dataset, SearchHandlerAsync handler) /// Regular expression to match against the dataset to be searched. /// Function to call when the search is triggered. /// The application instance for chaining purposes. - public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) + public AgentApplication OnSearch(Regex datasetPattern, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(datasetPattern); ArgumentNullException.ThrowIfNull(handler); @@ -325,7 +325,7 @@ public Application OnSearch(Regex datasetPattern, SearchHandlerAsync handler) /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) + public AgentApplication OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -365,7 +365,7 @@ public Application OnSearch(RouteSelectorAsync routeSelector, SearchHandlerAsync /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) + public AgentApplication OnSearch(MultipleRouteSelector routeSelectors, SearchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplication.cs similarity index 76% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplication.cs index f6761e59..80d99fd2 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/Application.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplication.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Reflection; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -18,23 +19,22 @@ namespace Microsoft.Agents.BotBuilder.App /// /// Application class for routing and processing incoming requests. /// - public class Application : IBot + public class AgentApplication : IBot { private readonly UserAuthenticationFeature _authentication; private readonly int _typingTimerDelay = 1000; private TypingTimer? _typingTimer; - private readonly ConcurrentQueue _invokeRoutes; - private readonly ConcurrentQueue _routes; + private readonly RouteList _routes; private readonly ConcurrentQueue _beforeTurn; private readonly ConcurrentQueue _afterTurn; /// - /// Creates a new Application instance. + /// Creates a new AgentApplication instance. /// /// Optional. Options used to configure the application. /// - public Application(ApplicationOptions options) + public AgentApplication(AgentApplicationOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -46,8 +46,7 @@ public Application(ApplicationOptions options) Options.TurnStateFactory = () => new TurnState(); } - _routes = new ConcurrentQueue(); - _invokeRoutes = new ConcurrentQueue(); + _routes = new RouteList(); _beforeTurn = new ConcurrentQueue(); _afterTurn = new ConcurrentQueue(); @@ -59,6 +58,8 @@ public Application(ApplicationOptions options) { _authentication = new UserAuthenticationFeature(this, options.UserAuthentication); } + + ApplyRouteAttributes(); } #region Application Features @@ -71,7 +72,7 @@ public Application(ApplicationOptions options) /// /// The application's configured options. /// - public ApplicationOptions Options { get; } + public AgentApplicationOptions Options { get; } /// /// Accessing authentication specific features. @@ -108,20 +109,15 @@ public UserAuthenticationFeature Authentication /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// Boolean indicating if the RouteSelectorAsync is for an activity that uses "invoke" which require special handling. Defaults to `false`. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false) + public AgentApplication AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(selector); ArgumentNullException.ThrowIfNull(handler); - Route route = new(selector, handler, isInvokeRoute); - if (isInvokeRoute) - { - _invokeRoutes.Enqueue(route); - } - else - { - _routes.Enqueue(route); - } + + _routes.AddRoute(selector, handler, isInvokeRoute, rank); + return this; } @@ -130,13 +126,14 @@ public Application AddRoute(RouteSelectorAsync selector, RouteHandler handler, b /// /// Name of the activity type to match. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnActivity(string type, RouteHandler handler) + public AgentApplication OnActivity(string type, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentException.ThrowIfNullOrWhiteSpace(type); ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(string.Equals(type, context.Activity?.Type, StringComparison.OrdinalIgnoreCase)); - OnActivity(routeSelector, handler); + OnActivity(routeSelector, handler, rank: rank); return this; } @@ -145,13 +142,14 @@ public Application OnActivity(string type, RouteHandler handler) /// /// Regular expression to match against the incoming activity type. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnActivity(Regex typePattern, RouteHandler handler) + public AgentApplication OnActivity(Regex typePattern, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(typePattern); ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult(context.Activity?.Type != null && typePattern.IsMatch(context.Activity?.Type)); - OnActivity(routeSelector, handler); + OnActivity(routeSelector, handler, rank: rank); return this; } @@ -160,12 +158,13 @@ public Application OnActivity(Regex typePattern, RouteHandler handler) /// /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler) + public AgentApplication OnActivity(RouteSelectorAsync routeSelector, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler, rank: rank); return this; } @@ -174,8 +173,9 @@ public Application OnActivity(RouteSelectorAsync routeSelector, RouteHandler han ///
/// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler) + public AgentApplication OnActivity(MultipleRouteSelector routeSelectors, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -183,21 +183,21 @@ public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler { foreach (string type in routeSelectors.Strings) { - OnActivity(type, handler); + OnActivity(type, handler, rank: rank); } } if (routeSelectors.Regexes != null) { foreach (Regex typePattern in routeSelectors.Regexes) { - OnActivity(typePattern, handler); + OnActivity(typePattern, handler, rank: rank); } } if (routeSelectors.RouteSelectors != null) { foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) { - OnActivity(routeSelector, handler); + OnActivity(routeSelector, handler, rank: rank); } } return this; @@ -206,10 +206,11 @@ public Application OnActivity(MultipleRouteSelector routeSelectors, RouteHandler /// /// Handles conversation update events. /// - /// Name of the conversation update event to handle, can use . + /// Name of the conversation update event to handle, can use . If /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public virtual Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + public virtual AgentApplication OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentException.ThrowIfNullOrWhiteSpace(conversationUpdateEvent); ArgumentNullException.ThrowIfNull(handler); @@ -246,7 +247,29 @@ public virtual Application OnConversationUpdate(string conversationUpdateEvent, break; } } - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler, rank: rank); + return this; + } + + /// + /// Handles conversation update events using a custom selector. + /// + /// This will be used in addition the checking for Activity.Type == ActivityTypes.ConversationUpdate. + /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. + /// The application instance for chaining purposes. + public virtual AgentApplication OnConversationUpdate(RouteSelectorAsync conversationUpdateSelector, RouteHandler handler, ushort rank = RouteRank.Unspecified) + { + ArgumentNullException.ThrowIfNull(conversationUpdateSelector); + ArgumentNullException.ThrowIfNull(handler); + + async Task wrapper(ITurnContext turnContext, CancellationToken cancellationToken) + { + return string.Equals(turnContext.Activity?.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase) + && await conversationUpdateSelector(turnContext, cancellationToken); + } + + AddRoute(wrapper, handler, rank: rank); return this; } @@ -255,14 +278,15 @@ public virtual Application OnConversationUpdate(string conversationUpdateEvent, ///
/// Name of the conversation update events to handle, can use as array item. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler) + public AgentApplication OnConversationUpdate(string[] conversationUpdateEvents, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(conversationUpdateEvents); ArgumentNullException.ThrowIfNull(handler); foreach (string conversationUpdateEvent in conversationUpdateEvents) { - OnConversationUpdate(conversationUpdateEvent, handler); + OnConversationUpdate(conversationUpdateEvent, handler, rank); } return this; } @@ -279,8 +303,9 @@ public Application OnConversationUpdate(string[] conversationUpdateEvents, Route ///
/// Substring of the incoming message text. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessage(string text, RouteHandler handler) + public AgentApplication OnMessage(string text, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentException.ThrowIfNullOrWhiteSpace(text); ArgumentNullException.ThrowIfNull(handler); @@ -291,7 +316,7 @@ public Application OnMessage(string text, RouteHandler handler) && context.Activity?.Text != null && context.Activity.Text.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 ); - OnMessage(routeSelector, handler); + OnMessage(routeSelector, handler, rank: rank); return this; } @@ -307,8 +332,9 @@ public Application OnMessage(string text, RouteHandler handler) ///
/// Regular expression to match against the text of an incoming message. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessage(Regex textPattern, RouteHandler handler) + public AgentApplication OnMessage(Regex textPattern, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(textPattern); ArgumentNullException.ThrowIfNull(handler); @@ -319,7 +345,7 @@ public Application OnMessage(Regex textPattern, RouteHandler handler) && context.Activity?.Text != null && textPattern.IsMatch(context.Activity.Text) ); - OnMessage(routeSelector, handler); + OnMessage(routeSelector, handler, rank: rank); return this; } @@ -331,12 +357,13 @@ public Application OnMessage(Regex textPattern, RouteHandler handler) ///
/// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler) + public AgentApplication OnMessage(RouteSelectorAsync routeSelector, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler, rank: rank); return this; } @@ -348,8 +375,9 @@ public Application OnMessage(RouteSelectorAsync routeSelector, RouteHandler hand ///
/// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler) + public AgentApplication OnMessage(MultipleRouteSelector routeSelectors, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -364,14 +392,14 @@ public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler { foreach (Regex textPattern in routeSelectors.Regexes) { - OnMessage(textPattern, handler); + OnMessage(textPattern, handler, rank: rank); } } if (routeSelectors.RouteSelectors != null) { foreach (RouteSelectorAsync routeSelector in routeSelectors.RouteSelectors) { - OnMessage(routeSelector, handler); + OnMessage(routeSelector, handler, rank: rank); } } return this; @@ -381,8 +409,9 @@ public Application OnMessage(MultipleRouteSelector routeSelectors, RouteHandler /// Handles message reactions added events. ///
/// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessageReactionsAdded(RouteHandler handler) + public AgentApplication OnMessageReactionsAdded(RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -391,7 +420,7 @@ public Application OnMessageReactionsAdded(RouteHandler handler) && context.Activity?.ReactionsAdded != null && context.Activity.ReactionsAdded.Count > 0 ); - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler, rank: rank); return this; } @@ -399,8 +428,9 @@ public Application OnMessageReactionsAdded(RouteHandler handler) /// Handles message reactions removed events. ///
/// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnMessageReactionsRemoved(RouteHandler handler) + public AgentApplication OnMessageReactionsRemoved(RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -409,7 +439,7 @@ public Application OnMessageReactionsRemoved(RouteHandler handler) && context.Activity?.ReactionsRemoved != null && context.Activity.ReactionsRemoved.Count > 0 ); - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler, rank: rank); return this; } @@ -417,8 +447,9 @@ public Application OnMessageReactionsRemoved(RouteHandler handler) /// Handles handoff activities. ///
/// Function to call when the route is triggered. + /// 0 - ushort.MaxValue for order of evaluation. Ranks of the same value are evaluated in order of addition. /// The application instance for chaining purposes. - public Application OnHandoff(HandoffHandler handler) + public AgentApplication OnHandoff(HandoffHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -434,7 +465,7 @@ public Application OnHandoff(HandoffHandler handler) Activity activity = ActivityUtilities.CreateInvokeResponseActivity(); await turnContext.SendActivityAsync(activity, cancellationToken); }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: true); + AddRoute(routeSelector, routeHandler, isInvokeRoute: true, rank: rank); return this; } @@ -449,7 +480,7 @@ public Application OnHandoff(HandoffHandler handler) ///
/// Function to call before turn execution. /// The application instance for chaining purposes. - public Application OnBeforeTurn(TurnEventHandlerAsync handler) + public AgentApplication OnBeforeTurn(TurnEventHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); _beforeTurn.Enqueue(handler); @@ -464,7 +495,7 @@ public Application OnBeforeTurn(TurnEventHandlerAsync handler) ///
/// Function to call after turn execution. /// The application instance for chaining purposes. - public Application OnAfterTurn(TurnEventHandlerAsync handler) + public AgentApplication OnAfterTurn(TurnEventHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); _afterTurn.Enqueue(handler); @@ -581,7 +612,7 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel } // Download any input files - IList? fileDownloaders = this.Options.FileDownloaders; + IList? fileDownloaders = Options.FileDownloaders; if (fileDownloaders != null && fileDownloaders.Count > 0) { foreach (IInputFileDownloader downloader in fileDownloaders) @@ -593,12 +624,12 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel bool eventHandlerCalled = false; - // TODO: why is this needed? Would not the selector be limiting to "Invoke" anyway, so iterating _routes would be the same thing. - // Run any RouteSelectors in this._invokeRoutes first if the incoming Teams activity.type is "Invoke". + // Run any Invoke RouteSelectors first if the incoming Teams activity.type is "Invoke". // Invoke Activities from Teams need to be responded to in less than 5 seconds. + // This is mostly because the selectors are async and could incur delays, so we need to limit this possibility. if (ActivityTypes.Invoke.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) { - foreach (Route route in _invokeRoutes) + foreach (Route route in _routes.Enumerate(true)) { if (await route.Selector(turnContext, cancellationToken)) { @@ -612,7 +643,7 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel // All other ActivityTypes and any unhandled Invokes are run through the remaining routes. if (!eventHandlerCalled) { - foreach (Route route in _routes) + foreach (Route route in _routes.Enumerate()) { if (await route.Selector(turnContext, cancellationToken)) { @@ -641,6 +672,23 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel } } + private void ApplyRouteAttributes() + { + // This will evaluate all methods that have an attribute, in declaration order (grouped by inheritance chain) + foreach (var method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + var activityRoutes = method.GetCustomAttributes(true); + foreach (var attribute in activityRoutes) + { + // Add route for all IRouteAttribute instances + if (attribute is IRouteAttribute routeAttribute) + { + routeAttribute.AddRoute(this, method); + } + } + } + } + #endregion } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationBuilder.cs similarity index 67% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationBuilder.cs index cc2e40f6..92263864 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationBuilder.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationBuilder.cs @@ -5,25 +5,26 @@ using System; using Microsoft.Agents.BotBuilder.App.AdaptiveCards; using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder.App.UserAuth; namespace Microsoft.Agents.BotBuilder.App { /// - /// A builder class for simplifying the creation of an Application instance. + /// A builder class for simplifying the creation of an AgentApplication instance. /// - public class ApplicationBuilder + public class AgentApplicationBuilder { /// /// The application's configured options. /// - public ApplicationOptions Options { get; } = new(); + public AgentApplicationOptions Options { get; } = new(); /// /// Configures the turn state factory to use for managing the bot's turn state. /// /// The turn state factory to use. /// The ApplicationBuilder instance. - public ApplicationBuilder WithTurnStateFactory(Func turnStateFactory) + public AgentApplicationBuilder WithTurnStateFactory(Func turnStateFactory) { Options.TurnStateFactory = turnStateFactory; return this; @@ -34,7 +35,7 @@ public ApplicationBuilder WithTurnStateFactory(Func turnStateFactory ///
/// The Logger factory /// The ApplicationBuilder instance. - public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + public AgentApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) { Options.LoggerFactory = loggerFactory; return this; @@ -45,7 +46,7 @@ public ApplicationBuilder WithLoggerFactory(ILoggerFactory loggerFactory) ///
/// The options for Adaptive Cards. /// The ApplicationBuilder instance. - public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveCardOptions) + public AgentApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveCardOptions) { Options.AdaptiveCards = adaptiveCardOptions; return this; @@ -57,47 +58,56 @@ public ApplicationBuilder WithAdaptiveCardOptions(AdaptiveCardsOptions adaptiveC /// /// The boolean for removing recipient mentions. /// The ApplicationBuilder instance. - public ApplicationBuilder SetRemoveRecipientMention(bool removeRecipientMention) + public AgentApplicationBuilder SetRemoveRecipientMention(bool removeRecipientMention) { Options.RemoveRecipientMention = removeRecipientMention; return this; } + /// + /// Configures the normalizing mentions across different channels. + /// + /// The boolean for normalizing mentions. + /// The ApplicationBuilder instance. + public AgentApplicationBuilder SetNormalizeMentions(bool normalizeMentions) + { + Options.NormalizeMentions = normalizeMentions; + return this; + } + + /// /// Configures the typing timer when messages are received. /// Default state for startTypingTimer is true /// /// The boolean for starting the typing timer. /// The ApplicationBuilder instance. - public ApplicationBuilder SetStartTypingTimer(bool startTypingTimer) + public AgentApplicationBuilder SetStartTypingTimer(bool startTypingTimer) { Options.StartTypingTimer = startTypingTimer; return this; } - //TODO - /* /// /// Configures authentication for the application. /// /// The bot adapter. /// The options for authentication. /// The ApplicationBuilder instance. - public ApplicationBuilder WithAuthentication(ChannelAdapter adapter, AuthenticationOptions authenticationOptions) + public AgentApplicationBuilder WithUserAuthentication(ChannelAdapter adapter, UserAuthenticationOptions authenticationOptions) { Options.Adapter = adapter; - Options.Authentication = authenticationOptions; + Options.UserAuthentication = authenticationOptions; return this; } - */ /// /// Builds and returns a new Application instance. /// /// The Application instance. - public Application Build() + public AgentApplication Build() { - return new Application(Options); + return new AgentApplication(Options); } } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationOptions.cs similarity index 95% rename from src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs rename to src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationOptions.cs index 3c64c1a1..6fe3e844 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ApplicationOptions.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AgentApplicationOptions.cs @@ -11,9 +11,9 @@ namespace Microsoft.Agents.BotBuilder.App { /// - /// Options for the class. + /// Options for the class. /// - public class ApplicationOptions + public class AgentApplicationOptions { public IChannelAdapter? Adapter { get; set; } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs new file mode 100644 index 00000000..1dc225d4 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; + +namespace Microsoft.Agents.BotBuilder.App +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public class ConversationUpdateRouteAttribute : Attribute, IRouteAttribute + { + public string Event { get; set; } + + public ushort Rank { get; set; } = RouteRank.Unspecified; + + public void AddRoute(AgentApplication app, MethodInfo method) + { + if (!string.IsNullOrWhiteSpace(Event)) + { + app.OnConversationUpdate(Event, method.CreateDelegate(app), Rank); + } + } + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IRouteAttribute.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IRouteAttribute.cs new file mode 100644 index 00000000..5881380f --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/IRouteAttribute.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Reflection; + +namespace Microsoft.Agents.BotBuilder.App +{ + internal interface IRouteAttribute + { + void AddRoute(AgentApplication app, MethodInfo method); + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteList.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteList.cs new file mode 100644 index 00000000..5911ea4a --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteList.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Microsoft.Agents.BotBuilder.App +{ + internal class RouteList + { + private readonly ReaderWriterLock rwl = new(); + private List routes = []; + + public void AddRoute(RouteSelectorAsync selector, RouteHandler handler, bool isInvokeRoute = false, ushort rank = RouteRank.Unspecified) + { + try + { + rwl.AcquireWriterLock(1000); + routes.Add(new RouteEntry() { Rank = rank, Route = new(selector, handler), IsInvokeRoute = isInvokeRoute }); + routes = [.. routes.OrderBy(entry => entry.Rank)]; + } + finally + { + rwl.ReleaseWriterLock(); + } + } + + public IEnumerable Enumerate(bool isInvokeRoute = false) + { + try + { + rwl.AcquireReaderLock(1000); + return new List(routes.Where(e => e.IsInvokeRoute == isInvokeRoute).Select(e => e.Route).ToList()); + } + finally + { + rwl.ReleaseReaderLock(); + } + } + } + + class RouteEntry + { + public ushort Rank; + public Route Route; + public bool IsInvokeRoute; + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteRank.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteRank.cs new file mode 100644 index 00000000..fc6ad077 --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/RouteRank.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Agents.BotBuilder.App +{ + public static class RouteRank + { + public const ushort First = 0; + public const ushort Last = ushort.MaxValue; + public const ushort Unspecified = ushort.MaxValue / 2; + } +} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs index 617490fe..6e682488 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/UserAuth/UserAuthenticationFeature.cs @@ -34,7 +34,7 @@ public class UserAuthenticationFeature private const string SignInCompletionEventName = "application/vnd.microsoft.SignInCompletion"; private readonly IUserAuthenticationDispatcher _dispatcher; private readonly UserAuthenticationOptions _options; - private readonly Application _app; + private readonly AgentApplication _app; /// /// Callback when user sign in success @@ -48,7 +48,7 @@ public class UserAuthenticationFeature public string Default { get; private set; } - public UserAuthenticationFeature(Application app, UserAuthenticationOptions options) + public UserAuthenticationFeature(AgentApplication app, UserAuthenticationOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); _dispatcher = new UserAuthenticationDispatcher([.. _options.Handlers]); @@ -241,7 +241,7 @@ private void AddManualSignInCompletionHandler() } }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); } public static bool IsSignInCompletionEvent(IActivity activity) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs index 363d9574..ebba9116 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Compat/ShowTypingMiddleware.cs @@ -7,7 +7,6 @@ using System.Collections.Concurrent; using System.Linq; using System.Security.Claims; -using System.Security.Principal; using System.Threading; using System.Threading.Tasks; diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs index 7617d5e9..025e57db 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Errors/ErrorHelper.cs @@ -37,6 +37,10 @@ internal static partial class ErrorHelper internal static AgentErrorDefinition NullUserTokenProviderIAccessTokenProvider = new AgentErrorDefinition(baseBotBuilderErrorCode-1, Properties.Resources.IAccessTokenProviderNotFound, "https://aka.ms/AgentsSDK-Error01"); internal static AgentErrorDefinition UserAuthenticationNotConfigured = new AgentErrorDefinition(baseBotBuilderErrorCode - 2, Properties.Resources.UserAuthenticationNotConfigured, "https://aka.ms/AgentsSDK-Error01"); + + // ActivityRouteAttribute + internal static AgentErrorDefinition AttributeSelectorNotFound = new AgentErrorDefinition(baseBotBuilderErrorCode - 3, Properties.Resources.AttributeSelectorNotFound, "https://aka.ms/AgentsSDK-Error01"); + internal static AgentErrorDefinition AttributeSelectorInvalid = new AgentErrorDefinition(baseBotBuilderErrorCode - 4, Properties.Resources.AttributeSelectorInvalid, "https://aka.ms/AgentsSDK-Error01"); } } diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs index aeead5ed..5697fada 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.Designer.cs @@ -60,6 +60,24 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to The ActivityRouteAttribute.Selector method does not match the RouteSelectorAsync delegate definition.. + /// + internal static string AttributeSelectorInvalid { + get { + return ResourceManager.GetString("AttributeSelectorInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ActivityRouteAttribute.Selector method is not found.. + /// + internal static string AttributeSelectorNotFound { + get { + return ResourceManager.GetString("AttributeSelectorNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to An instance of IAccessTokenProvider not found for {0}. /// @@ -70,7 +88,7 @@ internal static string IAccessTokenProviderNotFound { } /// - /// Looks up a localized string similar to The Application.Authentication property is unavailable because no user authentication handlers were configured.. + /// Looks up a localized string similar to The AgentApplication.Authentication property is unavailable because no user authentication handlers were configured.. /// internal static string UserAuthenticationNotConfigured { get { diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx index 0246ee3a..531deacb 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/Properties/Resources.resx @@ -122,7 +122,15 @@ Raised when the call get an access token provider returns null. - The Application.Authentication property is unavailable because no user authentication handlers were configured. - Raised when accessing Application.Authentication but it hasn't been configured. + The AgentApplication.Authentication property is unavailable because no user authentication handlers were configured. + Raised when accessing AgentApplication.Authentication but it hasn't been configured. + + + The ActivityRouteAttribute.Selector method is not found. + Raised when accessing ActivityRouteAttribute is being initialized but the indicated method isn't found on the AgentApplication or subclass. + + + The ActivityRouteAttribute.Selector method does not match the RouteSelectorAsync delegate definition. + Raised when accessing ActivityRouteAttribute is being initialized but the indicated method does not have a valid signature. \ No newline at end of file diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs index 8cc5a328..061b5a8e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/UserAuth/IUserAuthentication.cs @@ -16,10 +16,10 @@ public interface IUserAuthentication /// /// Signs in a user. - /// This method will be called automatically by the Application class. + /// This method will be called automatically by the AgentApplication class. /// /// Current turn context. - /// Application state. + /// AgentApplication state. /// The cancellation token /// The authentication token if user is signed in. Otherwise returns null. In that case the bot will attempt to sign the user in. Task SignInUserAsync(ITurnContext context, CancellationToken cancellationToken = default); @@ -28,7 +28,7 @@ public interface IUserAuthentication /// Signs out a user. /// /// Current turn context. - /// Application state. + /// AgentApplication state. /// The cancellation token Task SignOutUserAsync(ITurnContext context, CancellationToken cancellationToken = default); diff --git a/src/libraries/Core/Microsoft.Agents.Core/Errors/AgentErrorDefinition.cs b/src/libraries/Core/Microsoft.Agents.Core/Errors/AgentErrorDefinition.cs index d43c878b..9123fc70 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Errors/AgentErrorDefinition.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Errors/AgentErrorDefinition.cs @@ -33,7 +33,10 @@ public static class ExceptionHelper { public static T GenerateException(AgentErrorDefinition errorDefinition, Exception innerException, params string[] messageFormat) where T : Exception { - var excp = (T)Activator.CreateInstance(typeof(T), new object[] { string.Format(errorDefinition.description, messageFormat), innerException }); + var excp = innerException != null + ? (T)Activator.CreateInstance(typeof(T), new object[] { string.Format(errorDefinition.description, messageFormat), innerException }) + : (T)Activator.CreateInstance(typeof(T), new object[] { string.Format(errorDefinition.description, messageFormat) }); + excp.HResult = errorDefinition.code; excp.HelpLink = errorDefinition.helplink; return excp; diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs b/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs index c2426186..7f8f850b 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Models/EntityExtension.cs @@ -131,6 +131,8 @@ private static string RemoveAt(string text) /// new Activity.Text property value. public static string RemoveMentionText(this IActivity activity, string id) { + if (string.IsNullOrEmpty(id)) { return activity.Text; } + foreach (var mention in activity.GetMentions().Where(mention => mention.Mentioned.Id == id)) { if (mention.Text == null) diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/IActivityExtensions.cs b/src/libraries/Core/Microsoft.Agents.Core/Models/IActivityExtensions.cs index 841d0379..17bca69a 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/IActivityExtensions.cs +++ b/src/libraries/Core/Microsoft.Agents.Core/Models/IActivityExtensions.cs @@ -54,7 +54,7 @@ public static Mention[] GetMentions(this IActivity activity) /// new .Text property value. public static string RemoveRecipientMention(this T activity) where T : IActivity { - return activity.RemoveMentionText(activity.Recipient.Id); + return activity.RemoveMentionText(activity.Recipient?.Id); } /// diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs index f2a044a4..da69fad5 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/Meetings/MeetingsFeature.cs @@ -15,13 +15,13 @@ namespace Microsoft.Agents.Extensions.Teams.App.Meetings /// public class MeetingsFeature { - private readonly Application _app; + private readonly AgentApplication _app; /// /// Creates a new instance of the Meetings class. /// /// The top level application class to register handlers with. - public MeetingsFeature(Application app) + public MeetingsFeature(AgentApplication app) { this._app = app; } @@ -31,7 +31,7 @@ public MeetingsFeature(Application app) /// /// Function to call when a Microsoft Teams meeting start event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnStart(MeetingStartHandler handler) + public AgentApplication OnStart(MeetingStartHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -45,7 +45,7 @@ public Application OnStart(MeetingStartHandler handler) MeetingStartEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); return _app; } @@ -54,7 +54,7 @@ public Application OnStart(MeetingStartHandler handler) /// /// Function to call when a Microsoft Teams meeting end event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnEnd(MeetingEndHandler handler) + public AgentApplication OnEnd(MeetingEndHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -68,7 +68,7 @@ public Application OnEnd(MeetingEndHandler handler) MeetingEndEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); return _app; } @@ -77,7 +77,7 @@ public Application OnEnd(MeetingEndHandler handler) /// /// Function to call when a Microsoft Teams meeting participants join event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) + public AgentApplication OnParticipantsJoin(MeetingParticipantsEventHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -91,7 +91,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); return _app; } @@ -100,7 +100,7 @@ public Application OnParticipantsJoin(MeetingParticipantsEventHandler handler) /// /// Function to call when a Microsoft Teams meeting participants leave event activity is received from the connector. /// The application instance for chaining purposes. - public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) + public AgentApplication OnParticipantsLeave(MeetingParticipantsEventHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -114,7 +114,7 @@ public Application OnParticipantsLeave(MeetingParticipantsEventHandler handler) MeetingParticipantsEventDetails meeting = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value, () => new()); await handler(turnContext, turnState, meeting, cancellationToken); }; - _app.AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + _app.AddRoute(routeSelector, routeHandler); return _app; } } diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs index 1b670ff7..243fa142 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/MessageExtensions/MessageExtensionsFeature.cs @@ -51,13 +51,13 @@ public class MessageExtensionsFeature private static readonly string QUERY_SETTING_URL = "composeExtension/querySettingUrl"; private static readonly string QUERY_CARD_BUTTON_CLICKED = "composeExtension/onCardButtonClicked"; - private readonly Application _app; + private readonly AgentApplication _app; /// /// Creates a new instance of the MessageExtensions class. /// /// The top level application class to register handlers with. - public MessageExtensionsFeature(Application app) + public MessageExtensionsFeature(AgentApplication app) { this._app = app; } @@ -68,7 +68,7 @@ public MessageExtensionsFeature(Application app) /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) + public AgentApplication OnSubmitAction(string commandId, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -82,7 +82,7 @@ public Application OnSubmitAction(string commandId, SubmitActionHandlerAsync han /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) + public AgentApplication OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -96,7 +96,7 @@ public Application OnSubmitAction(Regex commandIdPattern, SubmitActionHandlerAsy /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmitAction(RouteSelectorAsync routeSelector, SubmitActionHandlerAsync handler) + public AgentApplication OnSubmitAction(RouteSelectorAsync routeSelector, SubmitActionHandlerAsync handler) { MessagingExtensionAction? messagingExtensionAction; RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => @@ -127,7 +127,7 @@ public Application OnSubmitAction(RouteSelectorAsync routeSelector, SubmitAction /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) + public AgentApplication OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitActionHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -162,7 +162,7 @@ public Application OnSubmitAction(MultipleRouteSelector routeSelectors, SubmitAc /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) + public AgentApplication OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -177,7 +177,7 @@ public Application OnBotMessagePreviewEdit(string commandId, BotMessagePreviewEd /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) + public AgentApplication OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -192,7 +192,7 @@ public Application OnBotMessagePreviewEdit(Regex commandIdPattern, BotMessagePre /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, BotMessagePreviewEditHandlerAsync handler) + public AgentApplication OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, BotMessagePreviewEditHandlerAsync handler) { RouteHandler routeHandler = async (ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) => { @@ -225,7 +225,7 @@ public Application OnBotMessagePreviewEdit(RouteSelectorAsync routeSelector, Bot /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) + public AgentApplication OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, BotMessagePreviewEditHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -260,7 +260,7 @@ public Application OnBotMessagePreviewEdit(MultipleRouteSelector routeSelectors, /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) + public AgentApplication OnBotMessagePreviewSend(string commandId, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -275,7 +275,7 @@ public Application OnBotMessagePreviewSend(string commandId, BotMessagePreviewSe /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) + public AgentApplication OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -290,7 +290,7 @@ public Application OnBotMessagePreviewSend(Regex commandIdPattern, BotMessagePre /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) + public AgentApplication OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -327,7 +327,7 @@ public Application OnBotMessagePreviewSend(RouteSelectorAsync routeSelector, Bot /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) + public AgentApplication OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, BotMessagePreviewSendHandler handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -361,7 +361,7 @@ public Application OnBotMessagePreviewSend(MultipleRouteSelector routeSelectors, /// ID of the commands to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) + public AgentApplication OnFetchTask(string commandId, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -375,7 +375,7 @@ public Application OnFetchTask(string commandId, FetchTaskHandlerAsync handler) /// Regular expression to match against the ID of the commands to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) + public AgentApplication OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -389,7 +389,7 @@ public Application OnFetchTask(Regex commandIdPattern, FetchTaskHandlerAsync han /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) + public AgentApplication OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -420,7 +420,7 @@ public Application OnFetchTask(RouteSelectorAsync routeSelector, FetchTaskHandle /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) + public AgentApplication OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -454,7 +454,7 @@ public Application OnFetchTask(MultipleRouteSelector routeSelectors, FetchTaskHa /// ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnQuery(string commandId, QueryHandlerAsync handler) + public AgentApplication OnQuery(string commandId, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandId); ArgumentNullException.ThrowIfNull(handler); @@ -468,7 +468,7 @@ public Application OnQuery(string commandId, QueryHandlerAsync handler) /// Regular expression to match against the ID of the command to register the handler for. /// Function to call when the command is received. /// The application instance for chaining purposes. - public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) + public AgentApplication OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(commandIdPattern); ArgumentNullException.ThrowIfNull(handler); @@ -482,7 +482,7 @@ public Application OnQuery(Regex commandIdPattern, QueryHandlerAsync handler) /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) + public AgentApplication OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -525,7 +525,7 @@ public Application OnQuery(RouteSelectorAsync routeSelector, QueryHandlerAsync h /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) + public AgentApplication OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -565,7 +565,7 @@ public Application OnQuery(MultipleRouteSelector routeSelectors, QueryHandlerAsy /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnSelectItem(SelectItemHandlerAsync handler) + public AgentApplication OnSelectItem(SelectItemHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -597,7 +597,7 @@ public Application OnSelectItem(SelectItemHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnQueryLink(QueryLinkHandlerAsync handler) + public AgentApplication OnQueryLink(QueryLinkHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -635,7 +635,7 @@ public Application OnQueryLink(QueryLinkHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) + public AgentApplication OnAnonymousQueryLink(QueryLinkHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -671,7 +671,7 @@ public Application OnAnonymousQueryLink(QueryLinkHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) + public AgentApplication OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -706,7 +706,7 @@ public Application OnQueryUrlSetting(QueryUrlSettingHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigureSettings(ConfigureSettingsHandler handler) + public AgentApplication OnConfigureSettings(ConfigureSettingsHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -739,7 +739,7 @@ public Application OnConfigureSettings(ConfigureSettingsHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnCardButtonClicked(CardButtonClickedHandler handler) + public AgentApplication OnCardButtonClicked(CardButtonClickedHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs index 8e62c462..5e666b5d 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TaskModules/TaskModulesFeature.cs @@ -43,7 +43,7 @@ public TaskModulesFeature(TeamsApplication app) /// Name of the verb to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(string verb, FetchHandlerAsync handler) + public AgentApplication OnFetch(string verb, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verb); ArgumentNullException.ThrowIfNull(handler); @@ -59,7 +59,7 @@ public Application OnFetch(string verb, FetchHandlerAsync handler) /// Regular expression to match against the verbs to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) + public AgentApplication OnFetch(Regex verbPattern, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -75,7 +75,7 @@ public Application OnFetch(Regex verbPattern, FetchHandlerAsync handler) /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) + public AgentApplication OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -109,7 +109,7 @@ public Application OnFetch(RouteSelectorAsync routeSelector, FetchHandlerAsync h /// Combination of String, Regex, and RouteSelectorAsync selectors. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) + public AgentApplication OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); @@ -145,7 +145,7 @@ public Application OnFetch(MultipleRouteSelector routeSelectors, FetchHandlerAsy /// Name of the verb to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(string verb, SubmitHandlerAsync handler) + public AgentApplication OnSubmit(string verb, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verb); ArgumentNullException.ThrowIfNull(handler); @@ -162,7 +162,7 @@ public Application OnSubmit(string verb, SubmitHandlerAsync handler) /// Regular expression to match against the verbs to register the handler for /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) + public AgentApplication OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(verbPattern); ArgumentNullException.ThrowIfNull(handler); @@ -178,7 +178,7 @@ public Application OnSubmit(Regex verbPattern, SubmitHandlerAsync handler) /// Function that's used to select a route. The function returning true triggers the route. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) + public AgentApplication OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelector); ArgumentNullException.ThrowIfNull(handler); @@ -212,7 +212,7 @@ public Application OnSubmit(RouteSelectorAsync routeSelector, SubmitHandlerAsync /// Combination of String, Regex, and RouteSelectorAsync verb(s) to register the handler for. /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) + public AgentApplication OnSubmit(MultipleRouteSelector routeSelectors, SubmitHandlerAsync handler) { ArgumentNullException.ThrowIfNull(routeSelectors); ArgumentNullException.ThrowIfNull(handler); diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs index f3e3621b..aaf9bdd8 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplication.cs @@ -20,7 +20,7 @@ namespace Microsoft.Agents.Extensions.Teams.App /// /// Application class for routing and processing incoming requests. /// - public class TeamsApplication : Application + public class TeamsApplication : AgentApplication { private static readonly string CONFIG_FETCH_INVOKE_NAME = "config/fetch"; private static readonly string CONFIG_SUBMIT_INVOKE_NAME = "config/submit"; @@ -65,8 +65,9 @@ public TeamsApplication(TeamsApplicationOptions options) : base(options) /// /// Name of the conversation update event to handle, can use . /// Function to call when the route is triggered. + /// /// The application instance for chaining purposes. - public override Application OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler) + public override AgentApplication OnConversationUpdate(string conversationUpdateEvent, RouteHandler handler, ushort rank = RouteRank.Unspecified) { ArgumentNullException.ThrowIfNull(conversationUpdateEvent); ArgumentNullException.ThrowIfNull(handler); @@ -135,7 +136,7 @@ public override Application OnConversationUpdate(string conversationUpdateEvent, break; } } - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler); return this; } @@ -144,7 +145,7 @@ public override Application OnConversationUpdate(string conversationUpdateEvent, /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageEdit(RouteHandler handler) + public AgentApplication OnMessageEdit(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -156,7 +157,7 @@ public Application OnMessageEdit(RouteHandler handler) && (teamsChannelData = turnContext.Activity.GetChannelData()) != null && string.Equals(teamsChannelData.EventType, "editMessage")); }; - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler); return this; } @@ -165,7 +166,7 @@ public Application OnMessageEdit(RouteHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageUndelete(RouteHandler handler) + public AgentApplication OnMessageUndelete(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -177,7 +178,7 @@ public Application OnMessageUndelete(RouteHandler handler) && (teamsChannelData = turnContext.Activity.GetChannelData()) != null && string.Equals(teamsChannelData.EventType, "undeleteMessage")); }; - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler); return this; } @@ -186,7 +187,7 @@ public Application OnMessageUndelete(RouteHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnMessageDelete(RouteHandler handler) + public AgentApplication OnMessageDelete(RouteHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => @@ -198,7 +199,7 @@ public Application OnMessageDelete(RouteHandler handler) && (teamsChannelData = turnContext.Activity.GetChannelData()) != null && string.Equals(teamsChannelData.EventType, "softDeleteMessage")); }; - AddRoute(routeSelector, handler, isInvokeRoute: false); + AddRoute(routeSelector, handler); return this; } @@ -207,7 +208,7 @@ public Application OnMessageDelete(RouteHandler handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnTeamsReadReceipt(ReadReceiptHandler handler) + public AgentApplication OnTeamsReadReceipt(ReadReceiptHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -221,7 +222,7 @@ public Application OnTeamsReadReceipt(ReadReceiptHandler handler) ReadReceiptInfo readReceiptInfo = ProtocolJsonSerializer.ToObject(turnContext.Activity.Value) ?? new(); await handler(turnContext, turnState, readReceiptInfo, cancellationToken); }; - AddRoute(routeSelector, routeHandler, isInvokeRoute: false); + AddRoute(routeSelector, routeHandler); return this; } @@ -230,7 +231,7 @@ public Application OnTeamsReadReceipt(ReadReceiptHandler handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigFetch(ConfigHandlerAsync handler) + public AgentApplication OnConfigFetch(ConfigHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( @@ -257,7 +258,7 @@ public Application OnConfigFetch(ConfigHandlerAsync handler) /// /// Function to call when the event is triggered. /// The application instance for chaining purposes. - public Application OnConfigSubmit(ConfigHandlerAsync handler) + public AgentApplication OnConfigSubmit(ConfigHandlerAsync handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (turnContext, cancellationToken) => Task.FromResult( @@ -284,7 +285,7 @@ public Application OnConfigSubmit(ConfigHandlerAsync handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFileConsentAccept(FileConsentHandler handler) + public AgentApplication OnFileConsentAccept(FileConsentHandler handler) => OnFileConsent(handler, "accept"); /// @@ -292,10 +293,10 @@ public Application OnFileConsentAccept(FileConsentHandler handler) /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnFileConsentDecline(FileConsentHandler handler) + public AgentApplication OnFileConsentDecline(FileConsentHandler handler) => OnFileConsent(handler, "decline"); - private Application OnFileConsent(FileConsentHandler handler, string fileConsentAction) + private AgentApplication OnFileConsent(FileConsentHandler handler, string fileConsentAction) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => @@ -330,7 +331,7 @@ private Application OnFileConsent(FileConsentHandler handler, string fileConsent /// /// Function to call when the route is triggered. /// The application instance for chaining purposes. - public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) + public AgentApplication OnO365ConnectorCardAction(O365ConnectorCardActionHandler handler) { ArgumentNullException.ThrowIfNull(handler); RouteSelectorAsync routeSelector = (context, _) => Task.FromResult @@ -360,7 +361,7 @@ public Application OnO365ConnectorCardAction(O365ConnectorCardActionHandler hand /// /// Function to cal lwhen the route is triggered /// - public Application OnFeedbackLoop(FeedbackLoopHandler handler) + public AgentApplication OnFeedbackLoop(FeedbackLoopHandler handler) { ArgumentNullException.ThrowIfNull(handler); diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs index 4fbc1d18..d4e1b555 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsApplicationOptions.cs @@ -6,7 +6,7 @@ namespace Microsoft.Agents.Extensions.Teams.App { - public class TeamsApplicationOptions : ApplicationOptions + public class TeamsApplicationOptions : AgentApplicationOptions { /// /// Optional. Options used to customize the processing of Task Modules requests. diff --git a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs index a88032e6..8a8cb3f8 100644 --- a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs +++ b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs @@ -11,9 +11,9 @@ namespace Microsoft.Agents.Hosting.AspNetCore { /// /// This contains the routes for the ChannelAPI. These are the endpoints that - /// ConnectorClient uses in the case of a bot-to-bot or Bot Framework Skill. + /// ConnectorClient uses in the case of a bot-to-bot. /// The implementation of this is via . - /// See the Microsoft.Agents.BotBuilder.ProxyChannelApiHandler class for an example of this for Dialogs and Skills. + /// See the Microsoft.Agents.BotBuilder.ProxyChannelApiHandler class for an example of this for Dialogs.SkillDialog and bot-to-bot. /// /// /// diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs new file mode 100644 index 00000000..f6bf433f --- /dev/null +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System.Threading; +using System.Threading.Tasks; + +namespace AuthenticationBot +{ + public class AuthBot : AgentApplication + { + public AuthBot(AgentApplicationOptions options) : base(options) + { + Authentication.OnUserSignInSuccess(async (turnContext, turnState, flowName, tokenResponse, cancellationToken) => + { + await turnContext.SendActivityAsync($"Successfully logged in to '{flowName}'", cancellationToken: cancellationToken); + }); + + Authentication.OnUserSignInFailure(async (turnContext, turnState, flowName, response, cancellationToken) => + { + await turnContext.SendActivityAsync($"Failed to login to '{flowName}': {response.Error.Message}", cancellationToken: cancellationToken); + }); + + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + + OnMessage("/reset", ResetAsync); + OnMessage("/signin", SignInAsync); + OnMessage("/signout", SignOutAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + protected async Task SignInAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + await Authentication.GetTokenOrStartSignInAsync(turnContext, turnState, "graph", cancellationToken); + } + + protected async Task SignOutAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + await Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); + await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); + } + + protected async Task ResetAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnState.User.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); + } + + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type 'auto' to demonstrate Auto SignIn. Type '/signin' to sign in for graph. Type '/signout' to sign-out. Anything else will be repeated back."), cancellationToken); + } + } + } + + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + if (turnContext.Activity.Text == "auto") + { + await turnContext.SendActivityAsync($"Successfully logged in to '{Authentication.Default}', token length: {turnState.Temp.AuthTokens[Authentication.Default].Length}", cancellationToken: cancellationToken); + } + else + { + await turnContext.SendActivityAsync($"You said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + } + } + } +} diff --git a/src/samples/AuthenticationBot/Program.cs b/src/samples/AuthenticationBot/Program.cs index 9ccca199..ab7c5d56 100644 --- a/src/samples/AuthenticationBot/Program.cs +++ b/src/samples/AuthenticationBot/Program.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using AuthenticationBot; using Microsoft.Agents.BotBuilder; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.App.UserAuth; using Microsoft.Agents.BotBuilder.State; using Microsoft.Agents.BotBuilder.UserAuth.TokenService; -using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -27,9 +27,8 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Create the bot as a transient. -builder.Services.AddTransient(sp => new TurnState(sp.GetService())); -builder.AddBot((sp) => +// Add ApplicationOptions +builder.Services.AddTransient(sp => { var adapter = sp.GetService(); var storage = sp.GetService(); @@ -55,73 +54,18 @@ storage)] }; - var appOptions = new ApplicationOptions() + return new AgentApplicationOptions() { Adapter = adapter, - StartTypingTimer = true, - UserAuthentication = authOptions, - TurnStateFactory = () => sp.GetService() + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(storage), + UserAuthentication = authOptions }; +}); - var app = new Application(appOptions); - - app.Authentication.OnUserSignInSuccess(async (turnContext, turnState, flowName, tokenResponse, cancellationToken) => - { - await turnContext.SendActivityAsync($"Successfully logged in to '{flowName}'", cancellationToken: cancellationToken); - }); - - app.Authentication.OnUserSignInFailure(async (turnContext, turnState, flowName, response, cancellationToken) => - { - await turnContext.SendActivityAsync($"Failed to login to '{flowName}': {response.Error.Message}", cancellationToken: cancellationToken); - }); - - app.OnMessage("/signin", async (turnContext, turnState, cancellationToken) => - { - await app.Authentication.GetTokenOrStartSignInAsync(turnContext, turnState, "graph", cancellationToken); - }); - - // Listen for user to say "/reset" and then delete state - app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnState.User.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current turn state", cancellationToken: cancellationToken); - }); - - // Listen for user to say "/signout" and then delete cached token - app.OnMessage("/signout", async (turnContext, turnState, cancellationToken) => - { - await app.Authentication.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); - await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); - }); +// Add the bot (which is transient) +builder.AddBot(); - // Display a welcome message - app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type 'auto' to demonstrate Auto SignIn. Type '/signin' to sign in for graph. Type '/signout' to sign-out. Anything else will be repeated back."), cancellationToken); - } - } - }); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => - { - if (turnContext.Activity.Text == "auto") - { - await turnContext.SendActivityAsync($"Successfully logged in to '{app.Authentication.Default}', token length: {turnState.Temp.AuthTokens[app.Authentication.Default].Length}", cancellationToken: cancellationToken); - } - else - { - await turnContext.SendActivityAsync($"You said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); - } - }); - - return app; -}); var app = builder.Build(); diff --git a/src/samples/AuthenticationBot/README.md b/src/samples/AuthenticationBot/README.md index 58acf9ca..dec88797 100644 --- a/src/samples/AuthenticationBot/README.md +++ b/src/samples/AuthenticationBot/README.md @@ -25,10 +25,13 @@ This Agent has been created using [Microsoft 365 Agents Framework](https://githu 1. Find the section labeled `Connections`, it should appear similar to this: ```json + "ConnectionName": "{{ConnectionName}}", + "TokenValidation": { "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" }, "Connections": { @@ -38,20 +41,18 @@ This Agent has been created using [Microsoft 365 Agents Framework](https://githu "Settings": { "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. "Scopes": [ "https://api.botframework.com/.default" - ], - "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. + ] } } ``` - 1. Set the **ClientId** to the AppId of the bot identity. + 1. Replace all **{{ClientId}}** with the AppId of the bot. + 1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered. 1. Set the **ClientSecret** to the Secret that was created for your identity. - 1. Set the **TenantId** to the Tenant Id where your application is registered. - 1. Set the **Audience** to the AppId of the bot identity. > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. diff --git a/src/samples/AuthenticationBot/appsettings.json b/src/samples/AuthenticationBot/appsettings.json index a6bcf7de..5bbc9b81 100644 --- a/src/samples/AuthenticationBot/appsettings.json +++ b/src/samples/AuthenticationBot/appsettings.json @@ -3,8 +3,9 @@ "TokenValidation": { "Audiences": [ - "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot - ] + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" }, "Connections": { @@ -12,7 +13,7 @@ "Settings": { "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", - "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. "Scopes": [ "https://api.botframework.com/.default" diff --git a/src/samples/Compat/AuthenticationBotCompat/AdapterWithErrorHandler.cs b/src/samples/Compat/AuthenticationBotCompat/AdapterWithErrorHandler.cs new file mode 100644 index 00000000..c1c4da5f --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/AdapterWithErrorHandler.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; +using Microsoft.Extensions.Logging; + +namespace AuthenticationBotCompat +{ + public class AdapterWithErrorHandler : CloudAdapter + { + public AdapterWithErrorHandler(ConversationState conversationState, IChannelServiceClientFactory channelServiceClientFactory, IActivityTaskQueue activityTaskQueue, ILogger logger) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + await turnContext.SendActivityAsync("The bot encountered an error or bug."); + await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteStateAsync(turnContext); + } + catch (Exception e) + { + logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/AuthenticationBotCompat.csproj b/src/samples/Compat/AuthenticationBotCompat/AuthenticationBotCompat.csproj new file mode 100644 index 00000000..0fdcb412 --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/AuthenticationBotCompat.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + latest + + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + Always + true + PreserveNewest + + + + + + Never + true + Never + + + diff --git a/src/samples/Compat/AuthenticationBotCompat/Bots/AuthBot.cs b/src/samples/Compat/AuthenticationBotCompat/Bots/AuthBot.cs new file mode 100644 index 00000000..77204778 --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Bots/AuthBot.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Logging; + +namespace AuthenticationBotCompat.Bots +{ + public class AuthBot : DialogBot where T : Dialog + { + public AuthBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + : base(conversationState, userState, dialog, logger) + { + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken); + } + } + } + + protected override async Task OnTokenResponseEventAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Logger.LogInformation("Running dialog with Token Response Event Activity."); + + // Run the Dialog with the new Token Response Event Activity. +#pragma warning disable CS0618 // Type or member is obsolete + await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + } + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/Bots/DialogBot.cs b/src/samples/Compat/AuthenticationBotCompat/Bots/DialogBot.cs new file mode 100644 index 00000000..a7b9ac3c --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Bots/DialogBot.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Logging; + +namespace AuthenticationBotCompat.Bots +{ + // This IBot implementation can run any type of Dialog. The use of type parameterization is to allows multiple different bots + // to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types + // each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity. + // The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, + // and the requirement is that all BotState objects are saved at the end of a turn. + public class DialogBot : ActivityHandler where T : Dialog + { + protected readonly BotState ConversationState; + protected readonly Dialog Dialog; + protected readonly ILogger Logger; + protected readonly BotState UserState; + + public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) + { + ConversationState = conversationState; + UserState = userState; + Dialog = dialog; + Logger = logger; + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) + { + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occurred during the turn. + await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); + await UserState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Logger.LogInformation("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. +#pragma warning disable CS0618 // Type or member is obsolete + await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + } + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/Controllers/BotController.cs b/src/samples/Compat/AuthenticationBotCompat/Controllers/BotController.cs new file mode 100644 index 00000000..7a33c63e --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Controllers/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace AuthenticationBotCompat.Controllers +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/Dialogs/LogoutDialog.cs b/src/samples/Compat/AuthenticationBotCompat/Dialogs/LogoutDialog.cs new file mode 100644 index 00000000..f1388dfc --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Dialogs/LogoutDialog.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.Connector; +using Microsoft.Agents.Core.Models; +using System.Threading; +using System.Threading.Tasks; + +namespace AuthenticationBotCompat.Dialogs +{ + public class LogoutDialog : ComponentDialog + { + public LogoutDialog(string id, string connectionName) + : base(id) + { + ConnectionName = connectionName; + } + + protected string ConnectionName { get; } + + protected override async Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnBeginDialogAsync(innerDc, options, cancellationToken); + } + + protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnContinueDialogAsync(innerDc, cancellationToken); + } + + private async Task InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + { + if (innerDc.Context.Activity.Type == ActivityTypes.Message) + { + var text = innerDc.Context.Activity.Text.ToLowerInvariant(); + + if (text == "logout") + { + // The UserTokenClient encapsulates the authentication processes. + var userTokenClient = innerDc.Context.Services.Get(); + await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); + + await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); + return await innerDc.CancelAllDialogsAsync(cancellationToken); + } + } + + return null; + } + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/Dialogs/MainDialog.cs b/src/samples/Compat/AuthenticationBotCompat/Dialogs/MainDialog.cs new file mode 100644 index 00000000..6d5461bd --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Dialogs/MainDialog.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace AuthenticationBotCompat.Dialogs +{ + public class MainDialog : LogoutDialog + { + protected readonly ILogger Logger; + + public MainDialog(IConfiguration configuration, ILogger logger) + : base(nameof(MainDialog), configuration["ConnectionName"]) + { + Logger = logger; + + AddDialog(new OAuthPrompt( + nameof(OAuthPrompt), + new OAuthPromptSettings + { + ConnectionName = ConnectionName, + Text = "Please Sign In", + Title = "Sign In", + Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) + })); + + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + PromptStepAsync, + LoginStepAsync, + DisplayTokenPhase1Async, + DisplayTokenPhase2Async, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); + } + + private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Get the token from the previous step. Note that we could also have gotten the + // token directly from the prompt itself. There is an example of this in the next method. + var tokenResponse = (TokenResponse)stepContext.Result; + if (tokenResponse != null) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken); + return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, cancellationToken); + } + + await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken); + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + + private async Task DisplayTokenPhase1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken); + + var result = (bool)stepContext.Result; + if (result) + { + // Call the prompt again because we need the token. The reasons for this are: + // 1. If the user is already logged in we do not need to store the token locally in the bot and worry + // about refreshing it. We can always just call the prompt again to get the token. + // 2. We never know how long it will take a user to respond. By the time the + // user responds the token may have expired. The user would then be prompted to login again. + // + // There is no reason to store the token locally in the bot because we can always just call + // the OAuth prompt to get the token or get a new token if needed. + return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), cancellationToken: cancellationToken); + } + + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + + private async Task DisplayTokenPhase2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var tokenResponse = (TokenResponse)stepContext.Result; + if (tokenResponse != null) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Here is your token {tokenResponse.Token}"), cancellationToken); + } + + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + } +} diff --git a/src/samples/Compat/AuthenticationBotCompat/Program.cs b/src/samples/Compat/AuthenticationBotCompat/Program.cs new file mode 100644 index 00000000..261d132a --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/Program.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AuthenticationBotCompat; +using AuthenticationBotCompat.Bots; +using AuthenticationBotCompat.Dialogs; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add ApplicationOptions +builder.Services.AddTransient(sp => +{ + return new AgentApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; +}); + +// Create the User state. (Used in this bot's Dialog implementation.) +builder.Services.AddSingleton(); + +// Create the Conversation state. (Used by the Dialog system itself.) +builder.Services.AddSingleton(); + +// The Dialog that will be run by the bot. +builder.Services.AddSingleton(); + +// Add the bot (which is transient) +builder.AddBot, AdapterWithErrorHandler>(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/src/samples/Compat/AuthenticationBotCompat/README.md b/src/samples/Compat/AuthenticationBotCompat/README.md new file mode 100644 index 00000000..24980342 --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/README.md @@ -0,0 +1,100 @@ +# Compat OAuth Authentication + +This bot demonstrates migrating a Bot Framework SDK bot to Agents SDK. The sample was created using `18.bot-authentication` from microsoft/BotBuilder-Samples on GitHub. + +This Agent has been created using [Microsoft 365 Agents Framework](https://github.com/microsoft/agents-for-net), it shows how to use authentication in your Agent using OAuth. + +- The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. +- The samples demonstrates performing OAuth without using the Dialogs package. + +- ## Prerequisites + +- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 +- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) + +## Running this sample + +1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot) + - Record the Application ID, the Tenant ID, and the Client Secret for use below + +1. [Add OAuth to your bot](https://aka.ms/AgentsSDK-AddAuth) + +1. Configuring the token connection in the Agent settings + > The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) + + 1. Open the `appsettings.json` file in the root of the sample project. + + 1. Find the section labeled `Connections`, it should appear similar to this: + + ```json + "ConnectionName": "{{ConnectionName}}", + + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Assembly": "Microsoft.Agents.Authentication.Msal", + "Type": "MsalAuth", + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + ``` + + 1. Replace all **{{ClientId}}** with the AppId of the bot. + 1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered. + 1. Set the **ClientSecret** to the Secret that was created for your identity. + + > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. + +1. Update `appsettings.json` + + | Property | Value Description | + |----------------------|-----------| + | ConnectionName | Set the configured bot's OAuth connection name. | + +1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and update it to match your DevTunnel port. If `./Properties/launchSettings.json`not fount Close and re-open the solution.launchSettings.json have been re-created. + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + +1. Update your Azure Bot ``Messaging endpoint`` with the tunnel Url: `{tunnel-url}/api/messages` + +1. Run the bot from a terminal or from Visual Studio + +1. Test via "Test in WebChat"" on your Azure Bot in the Azure Portal. + +## Running this Agent in Teams + +1. Manually update the manifest.json + - Edit the `manifest.json` contained in the `/appManifest` folder + - Replace with your AppId (that was created above) *everywhere* you see the place holder string `<>` + - Replace `<>` with your Agent url. For example, the tunnel host name. + - Zip up the contents of the `/appManifest` folder to create a `manifest.zip` +1. Upload the `manifest.zip` to Teams + - Select **Developer Portal** in the Teams left sidebar + - Select **Apps** (top row) + - Select **Import app**, and select the manifest.zip + +1. Select **Preview in Teams** in the upper right corner + +## Interacting with the Agent + +Type anything to sign-in, or `logout` to sign-out. + +## Further reading +To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. + diff --git a/src/samples/Compat/AuthenticationBotCompat/appManifest/color.png b/src/samples/Compat/AuthenticationBotCompat/appManifest/color.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cf81afbe2f5bafd8563920edfadb78b7b71be6 GIT binary patch literal 3415 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 YJx2difYK9bhlEpZpl7Q49&>" + ], + "webApplicationInfo": { + "id": "${{AAD_APP_CLIENT_ID}}", + "resource": "api://botid-${{AAD_APP_CLIENT_ID}}" + } +} \ No newline at end of file diff --git a/src/samples/Compat/AuthenticationBotCompat/appManifest/outline.png b/src/samples/Compat/AuthenticationBotCompat/appManifest/outline.png new file mode 100644 index 0000000000000000000000000000000000000000..2c3bf6fa65f152de0cb50056effd5aea7d287ec1 GIT binary patch literal 407 zcmV;I0cie-P)GP9wA4-6No2JPavK^y+J&IdIIqnt|)iz#;q%0#|~})uPXtHpGg|3DT=Cm zRbOQmZzjp~Oa~|w3J0d4$UMjUP`eo9-%ZEed<9c*o{#frSUWpe$h)9<7f||JElr8%Q+a+LHNJ~kNO5B zlRv;1hxJ`;YEbQ%GiTGTR{shYbEe%;Xrq2t9*a`EVNoJ89P+!W;^dkhG3QK~lh@uy z_@!DknGSuYuSg%;OK8pl!P9F+PR@yY6bgl7VhU4=M!!cg{}TWJ002ovPDHLkV1nXO Bp2+|J literal 0 HcmV?d00001 diff --git a/src/samples/Compat/AuthenticationBotCompat/appsettings.json b/src/samples/Compat/AuthenticationBotCompat/appsettings.json new file mode 100644 index 00000000..5bbc9b81 --- /dev/null +++ b/src/samples/Compat/AuthenticationBotCompat/appsettings.json @@ -0,0 +1,38 @@ +{ + "ConnectionName": "{{ConnectionName}}", + + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/samples/CopilotStudioEchoSkill/MyBot.cs b/src/samples/CopilotStudioEchoSkill/MyBot.cs new file mode 100644 index 00000000..79a2977d --- /dev/null +++ b/src/samples/CopilotStudioEchoSkill/MyBot.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System.Threading.Tasks; +using System.Threading; + +namespace CopilotStudioEchoSkill +{ + public class MyBot : AgentApplication + { + public MyBot(AgentApplicationOptions options) : base(options) + { + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + OnActivity(ActivityTypes.EndOfConversation, EndOfConversationAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hi, This is EchoSkill"), cancellationToken); + } + } + } + + protected async Task EndOfConversationAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + // This will be called if the root bot is ending the conversation. Sending additional messages should be + // avoided as the conversation may have been deleted. + // Perform cleanup of resources if needed. + await turnContext.SendActivityAsync("Received EndOfConversation", cancellationToken: cancellationToken); + } + + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) + { + var messageText = $"(EchoSkill) Ending conversation..."; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); + + // Indicate this conversation is over by sending an EndOfConversation Activity. + // This bot doesn't return a value, but if it did it could be put in Activity.Value. + var endOfConversation = Activity.CreateEndOfConversationActivity(); + endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; + await turnContext.SendActivityAsync(endOfConversation, cancellationToken); + } + else + { + var messageText = $"Echo(EchoSkill): {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); + messageText = "Echo(EchoSkill): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent."; + await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput.ToString()), cancellationToken); + } + } + } +} diff --git a/src/samples/CopilotStudioEchoSkill/Program.cs b/src/samples/CopilotStudioEchoSkill/Program.cs index 141bf014..3e53b298 100644 --- a/src/samples/CopilotStudioEchoSkill/Program.cs +++ b/src/samples/CopilotStudioEchoSkill/Program.cs @@ -4,7 +4,6 @@ using CopilotStudioEchoSkill; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -24,63 +23,19 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add bot routes and logic -builder.AddBot(sp => +// Add ApplicationOptions +builder.Services.AddTransient(sp => { - var options = new ApplicationOptions() + return new AgentApplicationOptions() { StartTypingTimer = false, TurnStateFactory = () => new TurnState(sp.GetService()) }; - - var app = new Application(options); - - // Display a welcome message - app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Hi, This is EchoSkill"), cancellationToken); - } - } - }); - - app.OnActivity(ActivityTypes.EndOfConversation, async (turnContext, turnState, cancellationToken) => - { - // This will be called if the root bot is ending the conversation. Sending additional messages should be - // avoided as the conversation may have been deleted. - // Perform cleanup of resources if needed. - await turnContext.SendActivityAsync("Received EndOfConversation", cancellationToken: cancellationToken); - }); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => - { - if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop")) - { - var messageText = $"(EchoSkill) Ending conversation..."; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); - - // Indicate this conversation is over by sending an EndOfConversation Activity. - // This bot doesn't return a value, but if it did it could be put in Activity.Value. - var endOfConversation = Activity.CreateEndOfConversationActivity(); - endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully; - await turnContext.SendActivityAsync(endOfConversation, cancellationToken); - } - else - { - var messageText = $"Echo(EchoSkill): {turnContext.Activity.Text}"; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput.ToString()), cancellationToken); - messageText = "Echo(EchoSkill): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent."; - await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput.ToString()), cancellationToken); - } - }); - - return app; }); +// Add the bot (which is transient) +builder.AddBot(); + var app = builder.Build(); // Required for providing the bot manifest. diff --git a/src/samples/CopilotStudioEchoSkill/README.md b/src/samples/CopilotStudioEchoSkill/README.md index c8a332ec..4fc7ab67 100644 --- a/src/samples/CopilotStudioEchoSkill/README.md +++ b/src/samples/CopilotStudioEchoSkill/README.md @@ -64,7 +64,7 @@ This sample is intended to introduce you to: } ``` - 1. Replace all `{{ClientId}}` with the AppId of the bot identity. + 1. Replace all `{{ClientId}}` with the AppId of the bot. 1. Replace all `{{TenantId}}` with the Tenant Id where your application is registered. 1. Set the **ClientSecret** to the Secret that was created for your identity. diff --git a/src/samples/EchoBot/MyBot.cs b/src/samples/EchoBot/MyBot.cs new file mode 100644 index 00000000..f870bcda --- /dev/null +++ b/src/samples/EchoBot/MyBot.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System.Threading.Tasks; +using System.Threading; + +namespace EchoBot +{ + public class MyBot : AgentApplication + { + public MyBot(AgentApplicationOptions options) : base(options) + { + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + OnMessage("/reset", ResetAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + } + + protected async Task ResetAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); + await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); + } + + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + // Increment count state. + int count = turnState.Conversation.IncrementMessageCount(); + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + } + } +} diff --git a/src/samples/EchoBot/Program.cs b/src/samples/EchoBot/Program.cs index f6f522f7..a7daa319 100644 --- a/src/samples/EchoBot/Program.cs +++ b/src/samples/EchoBot/Program.cs @@ -4,7 +4,6 @@ using EchoBot; using Microsoft.Agents.BotBuilder.App; using Microsoft.Agents.BotBuilder.State; -using Microsoft.Agents.Core.Models; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; using Microsoft.Agents.Storage; @@ -22,48 +21,19 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); -// Add bot routes and logic -builder.AddBot(sp => +// Add ApplicationOptions +builder.Services.AddTransient(sp => { - var options = new ApplicationOptions() + return new AgentApplicationOptions() { StartTypingTimer = false, TurnStateFactory = () => new TurnState(sp.GetService()) }; - - var app = new Application(options); - - // Display a welcome message - app.OnConversationUpdate(ConversationUpdateEvents.MembersAdded, async (turnContext, turnState, cancellationToken) => - { - foreach (ChannelAccount member in turnContext.Activity.MembersAdded) - { - if (member.Id != turnContext.Activity.Recipient.Id) - { - await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); - } - } - }); - - // Listen for user to say "/reset" and then delete conversation state - app.OnMessage("/reset", async (turnContext, turnState, cancellationToken) => - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); - }); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - app.OnActivity(ActivityTypes.Message, async (turnContext, turnState, cancellationToken) => - { - // Increment count state. - int count = turnState.Conversation.IncrementMessageCount(); - - await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); - }); - - return app; }); +// Add the bot (which is transient) +builder.AddBot(); + var app = builder.Build(); diff --git a/src/samples/SemanticKernel/WeatherBot/MyBot.cs b/src/samples/SemanticKernel/WeatherBot/MyBot.cs index ea4eeeec..3e502016 100644 --- a/src/samples/SemanticKernel/WeatherBot/MyBot.cs +++ b/src/samples/SemanticKernel/WeatherBot/MyBot.cs @@ -13,21 +13,16 @@ namespace WeatherBot { // This is the core handler for the Bot Message loop. Each new request will be processed by this class. - public class MyBot : Application + public class MyBot : AgentApplication { private readonly WeatherForecastAgent _weatherAgent; - public MyBot(ApplicationOptions options, WeatherForecastAgent weatherAgent) : base(options) + public MyBot(AgentApplicationOptions options, WeatherForecastAgent weatherAgent) : base(options) { _weatherAgent = weatherAgent; - - // Setup Activity routes - OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); - - // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS - OnActivity(ActivityTypes.Message, MessageActivityAsync); } + [ActivityRoute(Type = ActivityTypes.Message, Rank = RouteRank.Last)] protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { var chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory()); @@ -55,6 +50,7 @@ protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState t await turnContext.SendActivityAsync(response, cancellationToken); } + [ConversationUpdateRoute(Event = ConversationUpdateEvents.MembersAdded)] protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { foreach (ChannelAccount member in turnContext.Activity.MembersAdded) diff --git a/src/samples/SemanticKernel/WeatherBot/Program.cs b/src/samples/SemanticKernel/WeatherBot/Program.cs index ca625da7..7498fcc1 100644 --- a/src/samples/SemanticKernel/WeatherBot/Program.cs +++ b/src/samples/SemanticKernel/WeatherBot/Program.cs @@ -8,7 +8,6 @@ using Microsoft.SemanticKernel; using Microsoft.Extensions.Configuration; using WeatherBot.Agents; -using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos; using Azure.Identity; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Samples; @@ -56,16 +55,17 @@ // Add AspNet token validation builder.Services.AddBotAspNetAuthentication(builder.Configuration); +// Add ApplicationOptions builder.Services.AddTransient(sp => { - return new ApplicationOptions() + return new AgentApplicationOptions() { StartTypingTimer = true, TurnStateFactory = () => new TurnState(sp.GetService()) }; }); -// Add basic bot functionality +// Add the bot (which is transient) builder.AddBot(); var app = builder.Build(); diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ActivityRouteAttributeTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ActivityRouteAttributeTests.cs new file mode 100644 index 00000000..d983a38e --- /dev/null +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ActivityRouteAttributeTests.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.BotBuilder.Tests.App +{ + public class ActivityRouteAttributeTests + { + [Fact] + public async Task ActivityRouteAttribute_Type() + { + var app = new TestApp(new AgentApplicationOptions()); + var turnContext = new Mock(); + turnContext + .Setup(c => c.Activity) + .Returns(new Activity() { Type = ActivityTypes.Message }); + + await app.OnTurnAsync(turnContext.Object, CancellationToken.None); + + // Only one route is executed by Application. In this test, in definition order. + Assert.Single(app.calls); + Assert.Equal("OnMessageAsync", app.calls[0]); + } + + [Fact] + public async Task ActivityRouteAttribute_Regex() + { + var app = new TestApp(new AgentApplicationOptions()); + var turnContext = new Mock(); + turnContext + .Setup(c => c.Activity) + .Returns(new Activity() { Type = "testActivity" }); + + await app.OnTurnAsync(turnContext.Object, CancellationToken.None); + + // Only one route is executed by Application. In this test, in definition order. + Assert.Single(app.calls); + Assert.Equal("OnRegExAsync", app.calls[0]); + } + + [Fact] + public async Task ActivityRouteAttribute_Selector() + { + var app = new TestApp(new AgentApplicationOptions()); + var turnContext = new Mock(); + turnContext + .Setup(c => c.Activity) + .Returns(new Activity() { Type = ActivityTypes.Message, Text = "test_selector" }); + + await app.OnTurnAsync(turnContext.Object, CancellationToken.None); + + // Only one route is executed by Application. In this test, in definition order. + Assert.Single(app.calls); + Assert.Equal("OnSelectorAsync", app.calls[0]); + } + + [Fact] + public void ActivityRouteAttribute_SelectorNotFound() + { + Assert.Throws(() => new SelectorNotFoundTestApp(new AgentApplicationOptions())); + } + + [Fact] + public void ActivityRouteAttribute_SelectorInvalid() + { + Assert.Throws(() => new InvalidSelectorTestApp(new AgentApplicationOptions())); + } + } + + class TestApp(AgentApplicationOptions options) : AgentApplication(options) + { + public List calls = []; + + [ActivityRoute(Type = ActivityTypes.Message, Rank = RouteRank.Last)] + public Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + calls.Add("OnMessageAsync"); + return Task.CompletedTask; + } + + [ActivityRoute(Type = ActivityTypes.Message, Rank = RouteRank.Last)] + public Task OnMessageDuplicateAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + calls.Add("OnMessageDuplicateAsync"); + return Task.CompletedTask; + } + + protected Task TestSelectorAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.FromResult(turnContext.Activity.Text == "test_selector"); + } + + [ActivityRoute(Selector = "TestSelectorAsync")] + public Task OnSelectorAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + calls.Add("OnSelectorAsync"); + return Task.CompletedTask; + } + + [ActivityRoute(Regex = "test*.")] + public Task OnRegExAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + calls.Add("OnRegExAsync"); + return Task.CompletedTask; + } + } + + class SelectorNotFoundTestApp(AgentApplicationOptions options) : AgentApplication(options) + { + [ActivityRoute(Selector = "NotFoundSelectorAsync")] + public Task OnNotFoundSelectorAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } + + class InvalidSelectorTestApp(AgentApplicationOptions options) : AgentApplication(options) + { + // incorrect RouteSelectorAsync signature + protected Task InvalidSelectorAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.FromResult(1); + } + + [ActivityRoute(Selector = "InvalidSelectorAsync")] + public Task OnInvalidSelectorAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs index 747a9f04..f1b23d88 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/AdaptiveCardsTests.cs @@ -55,7 +55,7 @@ void CaptureSend(IActivity[] arg) }; var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -116,7 +116,7 @@ void CaptureSend(IActivity[] arg) var adaptiveCardInvokeResponseMock = new Mock(); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -151,7 +151,7 @@ public async Task Test_OnActionExecute_RouteSelector_ActivityNotMatched() var adaptiveCardInvokeResponseMock = new Mock(); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -193,7 +193,7 @@ public async Task Test_OnActionSubmit_Verb() }); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -235,7 +235,7 @@ public async Task Test_OnActionSubmit_Verb_NotHit() ChannelId = "channelId", }); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -274,7 +274,7 @@ public async Task Test_OnActionSubmit_RouteSelector_ActivityNotMatched() }); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -345,7 +345,7 @@ void CaptureSend(IActivity[] arg) }; var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -404,7 +404,7 @@ void CaptureSend(IActivity[] arg) }; var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -444,7 +444,7 @@ public async Task Test_OnSearch_RouteSelector_ActivityNotMatched() }; var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs index 5f7aac35..b4346ec0 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/ApplicationRouteTests.cs @@ -15,6 +15,91 @@ namespace Microsoft.Agents.BotBuilder.Tests.App { public class ApplicationRouteTests { + [Fact] + public async Task Test_RouteList_RouteRank() + { + List values = []; + RouteList routes = new(); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("1"); return Task.CompletedTask; }, + rank: 2 + ); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("2"); return Task.CompletedTask; }, + rank: 0 + ); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("3"); return Task.CompletedTask; }, + rank: 1 + ); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("4"); return Task.CompletedTask; }, + rank: 1 + ); + + foreach ( var route in routes.Enumerate()) + { + await route.Handler(null, null, CancellationToken.None); + } + + Assert.Equal(4, values.Count); + Assert.Equal("2", values[0]); + Assert.Equal("3", values[1]); + Assert.Equal("4", values[2]); + Assert.Equal("1", values[3]); + } + + [Fact] + public async Task Test_RouteList_ByInvoke() + { + List values = []; + RouteList routes = new(); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("1"); return Task.CompletedTask; } + ); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("2"); return Task.CompletedTask; } + ); + + routes.AddRoute( + (turnContext, CancellationToken) => { return Task.FromResult(true); }, + (turnContext, turnState, CancellationToken) => { values.Add("invoke"); return Task.CompletedTask; }, + isInvokeRoute: true + ); + + // non-invoke + foreach (var route in routes.Enumerate()) + { + await route.Handler(null, null, CancellationToken.None); + } + + Assert.Equal(2, values.Count); + Assert.Equal("1", values[0]); + Assert.Equal("2", values[1]); + + // Invoke + values.Clear(); + foreach (var route in routes.Enumerate(true)) + { + await route.Handler(null, null, CancellationToken.None); + } + + Assert.Single(values); + Assert.Equal("invoke", values[0]); + } + [Fact] public async Task Test_Application_Route() { @@ -33,7 +118,7 @@ public async Task Test_Application_Route() var turnContext1 = new TurnContext(adapter, activity1); var turnContext2 = new TurnContext(adapter, activity2); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -71,7 +156,7 @@ public async Task Test_Application_Routes_Are_Called_InOrder() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -139,7 +224,7 @@ public async Task Test_Application_InvokeRoute() var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); var turnContext2 = new TurnContext(adapter, activity2); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -180,7 +265,7 @@ public async Task Test_Application_InvokeRoutes_Are_Called_InOrder() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -236,7 +321,7 @@ public async Task Test_Application_InvokeRoutes_Are_Called_First() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -284,7 +369,7 @@ public async Task Test_Application_No_InvokeRoute_Matched_Fallback_To_Routes() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { StartTypingTimer = false, TurnStateFactory = () => turnState.Result, @@ -348,7 +433,7 @@ public async Task Test_OnActivity_String_Selector() var turnContext1 = new TurnContext(adapter, activity1); var turnContext2 = new TurnContext(adapter, activity2); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -395,7 +480,7 @@ public async Task Test_OnActivity_Regex_Selector() var turnContext1 = new TurnContext(adapter, activity1); var turnContext2 = new TurnContext(adapter, activity2); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -443,7 +528,7 @@ public async Task Test_OnActivity_Function_Selector() var turnContext1 = new TurnContext(adapter, activity1); var turnContext2 = new TurnContext(adapter, activity2); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -500,7 +585,7 @@ public async Task Test_OnActivity_Multiple_Selectors() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -567,7 +652,7 @@ public async Task Test_OnConversationUpdate_MembersAdded() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -626,7 +711,7 @@ public async Task Test_OnConversationUpdate_MembersRemoved() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -665,7 +750,7 @@ public async Task Test_OnConversationUpdate_UnknownEventName() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -724,7 +809,7 @@ public async Task Test_OnMessage_String_Selector() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -784,7 +869,7 @@ public async Task Test_OnMessage_Regex_Selector() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -833,7 +918,7 @@ public async Task Test_OnMessage_Function_Selector() var turnContext1 = new TurnContext(adapter, activity1); var turnContext2 = new TurnContext(adapter, activity2); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -893,7 +978,7 @@ public async Task Test_OnMessage_Multiple_Selectors() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -960,7 +1045,7 @@ public async Task Test_OnMessageReactionsAdded() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -1019,7 +1104,7 @@ public async Task Test_OnMessageReactionsRemoved() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -1089,7 +1174,7 @@ void CaptureSend(IActivity[] arg) Status = 200 }; var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs index 61e062e5..836ad045 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/Command.cs @@ -34,7 +34,7 @@ public async Task CommandBotTest() Value = new MathCommand { First = 10, Second = 2 } }; - await new TestFlow(adapter, new CommandBot(new ApplicationOptions())) + await new TestFlow(adapter, new CommandBot(new AgentApplicationOptions())) .Send(commandActivity) .AssertReply((activity) => { @@ -55,9 +55,9 @@ public async Task CommandBotTest() } } - class CommandBot : Application + class CommandBot : AgentApplication { - public CommandBot(ApplicationOptions options) : base(options) + public CommandBot(AgentApplicationOptions options) : base(options) { OnActivity(ActivityTypes.Command, OnCommandAsync); } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs index bef23c85..f7b0ae35 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestApplication.cs @@ -5,7 +5,7 @@ namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils { - public class TestApplication : Application + public class TestApplication : AgentApplication { public TestApplication(TestApplicationOptions options) : base(options) { @@ -15,5 +15,5 @@ public TestApplication(TestApplicationOptions options) : base(options) } } - public class TestApplicationOptions : ApplicationOptions { } + public class TestApplicationOptions : AgentApplicationOptions { } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs index 98de81a9..ce746400 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/TestUtils/TestUserAuthenticationFeature.cs @@ -6,7 +6,7 @@ namespace Microsoft.Agents.BotBuilder.Tests.App.TestUtils { internal sealed class TestUserAuthenticationFeature : UserAuthenticationFeature { - public TestUserAuthenticationFeature(Application app, UserAuthenticationOptions options) : base(app, options) + public TestUserAuthenticationFeature(AgentApplication app, UserAuthenticationOptions options) : base(app, options) { } } diff --git a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs index 8dbfa323..7ce366c3 100644 --- a/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs +++ b/src/tests/Microsoft.Agents.BotBuilder.Tests/App/UserAuthenticationFeatureTests.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.Agents.BotBuilder.App.UserAuth; using Microsoft.Agents.BotBuilder.Testing; using Microsoft.Agents.BotBuilder.Tests.App.TestUtils; diff --git a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs index 39d201a8..5067e2bb 100644 --- a/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs +++ b/src/tests/Microsoft.Agents.Extensions.Teams.Tests/App/ApplicationRouteTests.cs @@ -42,7 +42,7 @@ public async Task Test_OnConversationUpdate_ChannelCreated() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -86,7 +86,7 @@ public async Task Test_OnConversationUpdate_ChannelRenamed() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -130,7 +130,7 @@ public async Task Test_OnConversationUpdate_ChannelDeleted() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -175,7 +175,7 @@ public async Task Test_OnConversationUpdate_ChannelRestored() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -218,7 +218,7 @@ public async Task Test_OnConversationUpdate_TeamRenamed() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -261,7 +261,7 @@ public async Task Test_OnConversationUpdate_TeamDeleted() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -304,7 +304,7 @@ public async Task Test_OnConversationUpdate_TeamHardDeleted() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -347,7 +347,7 @@ public async Task Test_OnConversationUpdate_TeamArchived() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -390,7 +390,7 @@ public async Task Test_OnConversationUpdate_TeamUnarchived() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -433,7 +433,7 @@ public async Task Test_OnConversationUpdate_TeamRestored() var adapter = new NotImplementedAdapter(); var turnContext = new TurnContext(adapter, activity); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, @@ -566,7 +566,7 @@ public async Task Test_OnConversationUpdate_MultipleEvents() var turnContext2 = new TurnContext(adapter, activity2); var turnContext3 = new TurnContext(adapter, activity3); var turnState = TurnStateConfig.GetTurnStateWithConversationStateAsync(turnContext1); - var app = new Application(new() + var app = new AgentApplication(new() { RemoveRecipientMention = false, StartTypingTimer = false, From 23629fd0dc3eccd34838e5f05e4657e419ae42d3 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 27 Feb 2025 13:47:28 -0600 Subject: [PATCH 54/60] Removed "reset" on EchoBot --- src/samples/EchoBot/MyBot.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/samples/EchoBot/MyBot.cs b/src/samples/EchoBot/MyBot.cs index f870bcda..5aaab20c 100644 --- a/src/samples/EchoBot/MyBot.cs +++ b/src/samples/EchoBot/MyBot.cs @@ -15,7 +15,6 @@ public class MyBot : AgentApplication public MyBot(AgentApplicationOptions options) : base(options) { OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); - OnMessage("/reset", ResetAsync); // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS OnActivity(ActivityTypes.Message, OnMessageAsync); @@ -32,12 +31,6 @@ protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState tu } } - protected async Task ResetAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) - { - await turnState.Conversation.DeleteStateAsync(turnContext, cancellationToken); - await turnContext.SendActivityAsync("Ok I've deleted the current conversation state", cancellationToken: cancellationToken); - } - protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) { // Increment count state. From 0595686a578ebb535959515ccb6fb1f7d5a3440c Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 08:36:28 -0600 Subject: [PATCH 55/60] Added TeamsConversationSsoQuickstart compat sample --- src/Microsoft.Agents.SDK.sln | 7 + .../ProxyChannelApiHandler.cs | 3 +- .../AspNetCore/ServiceCollectionExtensions.cs | 94 ++++++---- .../Bots/DialogBot.cs | 0 .../Bots/TeamsBot.cs | 0 .../Controllers/BotController.cs | 0 .../Dialogs/LogoutDialog.cs | 0 .../Dialogs/MainDialog.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Installed.png | Bin .../Images/3.Logged_In.png | Bin .../Images/4.Your_Token.png | Bin .../Images/BotConversationSsoQuickStart.gif | Bin .../TeamsConversationSsoQuickstart/Program.cs | 62 +++++++ .../TeamsConversationSsoQuickstart}/README.md | 0 .../SimpleGraphClient.cs | 0 .../TeamsConversationSsoQuickstart.csproj | 34 ++++ .../TeamsSSOAdapter.cs | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../AdaptiveCardsBot/AdaptiveCardsBot.csproj | 26 +++ .../AdaptiveCardsBot/BotController.cs | 25 +++ .../Model/AdaptiveCardSubmitData.cs | 13 ++ .../AdaptiveCardsBot/Model/Package.cs | 15 ++ .../test-bots/AdaptiveCardsBot/Program.cs | 51 ++++++ .../Properties/launchSettings.TEMPLATE.json | 16 ++ .../test-bots/AdaptiveCardsBot/README.md | 40 +++++ .../Resources/DynamicSearchCard.json | 60 +++++++ .../Resources/StaticSearchCard.json | 97 ++++++++++ .../AdaptiveCardsBot/TypeAheadBot.cs | 167 ++++++++++++++++++ .../AdaptiveCardsBot/appManifest/color.png | Bin 0 -> 1066 bytes .../appManifest/manifest.json | 50 ++++++ .../AdaptiveCardsBot/appManifest/outline.png | Bin 0 -> 249 bytes .../AdaptiveCardsBot/appsettings.json | 37 ++++ .../assets/TypeaheadSearch.png | Bin 0 -> 155464 bytes .../BotToBot/.gitignore | 0 .../BotToBot/Bot1/.config/dotnet-tools.json | 0 .../BotToBot/Bot1/Bot1.csproj | 0 .../Bot1/BotHostAdapterWithErrorHandler.cs | 0 .../BotToBot/Bot1/Bots/Bot1.cs | 0 .../Controllers/Bot2ResponseController.cs | 0 .../Bot1/Controllers/BotController.cs | 0 .../BotToBot/Bot1/Program.cs | 0 .../Bot1/Properties/launchSettings.json | 0 .../BotToBot/Bot1/README.md | 0 .../BotToBot/Bot1/appsettings.json | 0 .../BotToBot/Bot1/wwwroot/default.htm | 0 .../BotToBot/Bot2/Bot2.csproj | 0 .../Bot2/BotAdapterWithErrorHandler.cs | 0 .../BotToBot/Bot2/Bots/Bot2.cs | 0 .../Bot2/Controllers/BotController.cs | 0 .../BotToBot/Bot2/Program.cs | 0 .../Bot2/Properties/launchSettings.json | 0 .../BotToBot/Bot2/README.md | 0 .../BotToBot/Bot2/appsettings.json | 0 .../BotToBot/Bot2/wwwroot/default.htm | 0 .../manifest/echobot-manifest-1.0.json | 0 .../BotToBot/README.md | 0 .../BotToBot/SimpleBotToBot.sln | 0 .../AdaptiveCardActions.csproj | 0 .../Bots/AdaptiveCardActionsBot.cs | 0 .../Cards/AdaptiveCardActions.json | 0 .../Cards/SuggestedActions.json | 0 .../Cards/ToggleVisibleCard.json | 0 .../Controllers/BotController.cs | 0 .../AdaptiveCardActions/Images/1.Install.png | Bin .../Images/10.ToggleVisibiliyCard.png | Bin .../Images/11.VisibleOnClick.png | Bin .../Images/2.WelcomeMessage.png | Bin .../AdaptiveCardActions/Images/3.Red.png | Bin .../AdaptiveCardActions/Images/4.Green.png | Bin .../AdaptiveCardActions/Images/5.Blue.png | Bin .../Images/6.CardActions.png | Bin .../Images/7.ActionSubmit.png | Bin .../Images/8.ActionShowCard.png | Bin .../Images/AdaptiveCardActions.gif | Bin .../Teams/AdaptiveCardActions/Program.cs | 0 .../Teams/AdaptiveCardActions/README.md | 0 .../AdaptiveCardActions/appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../AdaptiveCardActions/appsettings.json | 0 .../Bots/TeamsConversationBot.cs | 0 .../Controllers/BotController.cs | 0 .../ConversationBot/ConversationBot.csproj | 0 .../Teams/ConversationBot/Program.cs | 0 .../Teams/ConversationBot/README.md | 0 .../Resources/UserMentionCardTemplate.json | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../ConversationBot/appManifest/manifest.json | 0 .../Teams/ConversationBot/appsettings.json | 0 .../LinkUnfurling/AppManifest/icon-color.png | Bin .../AppManifest/icon-outline.png | Bin .../LinkUnfurling/AppManifest/manifest.json | 0 .../LinkUnfurling/Bots/LinkUnfurlingBot.cs | 0 .../Controllers/BotController.cs | 0 .../Teams/LinkUnfurling/Images/Add-App.png | Bin .../LinkUnfurling/Images/Link-Unfurling.png | Bin .../LinkUnfurling/Images/OpenAppIcon.png | Bin .../LinkUnfurling/Images/OpenNewMail.png | Bin .../Images/SearchInExtension.png | Bin .../Images/msgext-link-unfurling.gif | Bin .../Teams/LinkUnfurling/LinkUnfurling.csproj | 0 .../Teams/LinkUnfurling/Program.cs | 0 .../Teams/LinkUnfurling/README.md | 0 .../Teams/LinkUnfurling/appsettings.json | 0 .../Bots/MeetingContextBot.cs | 0 .../Controllers/BotController.cs | 0 .../Meeting-Context-App/Images/1.setup.png | Bin .../Images/2.add_to_meeting.png | Bin .../Images/3.tab_configuration.png | Bin .../Images/4.tab_context_details.png | Bin .../Meeting-Context-App/Images/AddToChat.png | Bin .../Images/Meeting-Details.png | Bin .../Images/MeetingContext.png | Bin .../Images/Participant-Details.png | Bin .../Images/ParticipantContext.png | Bin .../Images/Setup-Tab-Bot.png | Bin .../Meeting-Context-App/Images/Tab-View.png | Bin .../Images/meetingTabContext.png | Bin .../Images/meeting_context_csharp.gif | Bin .../MeetingContextApp.csproj | 0 .../Teams/Meeting-Context-App/Program.cs | 0 .../Teams/Meeting-Context-App/README.md | 0 .../Meeting-Context-App/appPackage/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage/outline.png | Bin .../Meeting-Context-App/appsettings.json | 0 .../Bots/InMeetingNotifications.cs | 0 .../Cards/AgendaCard.json | 0 .../Cards/QuestionTemplate.json | 0 .../Cards/SendTargetNotificationCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Home_Page.png | Bin .../Images/3.Send_Meeting_Notification.png | Bin .../Images/4.Option_Card.png | Bin .../Images/5.Output_in_Chat.png | Bin .../Images/6.Card_in_Meeting_Chat.png | Bin .../Images/7.Popup_Window.png | Bin .../Images/MeetingNotification.gif | Bin .../InMeetingNotificationsBot.csproj | 0 .../Models/ActionBase.cs | 0 .../Models/AgendaItem.cs | 0 .../Models/MeetingAgenda.cs | 0 .../Models/MeetingNotification.cs | 0 .../Models/ParticipantDetail.cs | 0 .../Models/PushAgendaAction.cs | 0 .../Models/SubmitFeedback.cs | 0 .../Pages/InMeetingNotificationPage.cshtml | 0 .../Pages/InMeetingNotificationPage.cshtml.cs | 0 .../Pages/SendNotificationPage.cshtml | 0 .../Pages/SendNotificationPage.cshtml.cs | 0 .../Pages/Shared/_Layout.cshtml | 0 .../Pages/_ViewStart.cshtml | 0 .../Teams/Meetings-Notification/Program.cs | 0 .../Teams/Meetings-Notification/README.md | 0 .../Teams/Meetings-Notification/Titles.cs | 0 .../appPackage/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage/outline.png | Bin .../Meetings-Notification/appsettings.json | 0 .../Bots/TeamsMessagingExtensionsSearchBot.cs | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.AddSuccessfully.png | Bin .../Images/3.SelectSample.png | Bin .../Images/4.QueryResults.png | Bin .../Images/5.SentSelectedPackage.png | Bin .../Images/6.SearchQueryResult-2.png | Bin .../Images/msgext-search.gif | Bin .../MessagingExtensionsSearch.csproj | 0 .../MessagingExtensionsSearch/Program.cs | 0 .../Teams/MessagingExtensionsSearch/README.md | 0 .../Resources/RestaurantCard.json | 0 .../Resources/UserMentionCardTemplate.json | 0 .../Resources/connectorCard.json | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../appManifest/manifest.json | 0 .../appsettings.json | 0 .../wwwroot/default.html | 0 .../TaskModule/Bots/TeamsTaskModuleBot.cs | 0 .../TaskModule/Controllers/BotController.cs | 0 .../TaskModule/Helper/AdaptiveCardHelper.cs | 0 .../TaskModule/Helper/ApplicationSettings.cs | 0 .../Teams/TaskModule/Helper/DeeplinkHelper.cs | 0 .../Teams/TaskModule/Helper/UIConstants.cs | 0 .../Teams/TaskModule/Images/1.InstallApp.png | Bin .../Teams/TaskModule/Images/10.GroupChat.png | Bin .../TaskModule/Images/11.GroupDialogs.png | Bin .../TaskModule/Images/12.GroupCustomForm.png | Bin .../TaskModule/Images/13.GroupResults.png | Bin .../Teams/TaskModule/Images/2.Dialogs.png | Bin .../TaskModule/Images/3.AdaptiveCard.png | Bin .../Images/4.ThanksAdaptiveCard.png | Bin .../Teams/TaskModule/Images/5.CustomForm.png | Bin .../Teams/TaskModule/Images/6.Results.png | Bin .../Teams/TaskModule/Images/7.YouTube.png | Bin .../Teams/TaskModule/Images/8.Task.png | Bin .../Teams/TaskModule/Images/9.PowerApp.png | Bin .../TaskModule/Images/Bot_Tab_TaskModule.gif | Bin .../Models/AdaptiveCardTaskFetchValue.cs | 0 .../TaskModule/Models/CardTaskFetchValue.cs | 0 .../Teams/TaskModule/Models/TaskModuleIds.cs | 0 .../Models/TaskModuleResponseFactory.cs | 0 .../Models/TaskModuleUIConstants.cs | 0 .../Teams/TaskModule/Models/UISettings.cs | 0 .../Teams/TaskModule/Pages/Configure.cshtml | 0 .../Teams/TaskModule/Pages/CustomForm.cshtml | 0 .../TaskModule/Pages/CustomForm.cshtml.cs | 0 .../Teams/TaskModule/Pages/HelloWorld.cshtml | 0 .../Teams/TaskModule/Pages/PowerApp.cshtml | 0 .../TaskModule/Pages/Shared/_EmbedPage.cshtml | 0 .../TaskModule/Pages/Shared/_Layout.cshtml | 0 .../Teams/TaskModule/Pages/Tasks.cshtml | 0 .../Teams/TaskModule/Pages/YouTube.cshtml | 0 .../Teams/TaskModule/Pages/_ViewStart.cshtml | 0 .../Teams/TaskModule/Program.cs | 0 .../Teams/TaskModule/README.md | 0 .../Resources/AdaptiveCard_TaskModule.json | 0 .../TaskModule/Resources/adaptiveCard.json | 0 .../Teams/TaskModule/TaskModule.csproj | 0 .../Teams/TaskModule/appManifest/color.png | Bin .../TaskModule/appManifest/manifest.json | 0 .../Teams/TaskModule/appManifest/outline.png | Bin .../Teams/TaskModule/appsettings.json | 0 .../Teams/TaskModule/assets/sample.json | 0 .../Teams/TaskModule/wwwroot/css/Site.css | 0 .../Teams/TaskModule/wwwroot/css/custom.css | 0 .../TaskModule/wwwroot/css/msteams-16.css | 0 .../Teams/TaskModule/wwwroot/default.htm | 0 .../Teams/bot-all-cards/BotAllCards.csproj | 0 .../Teams/bot-all-cards/Bots/DialogBot.cs | 0 .../Teams/bot-all-cards/Bots/TeamsBot.cs | 0 .../Teams/bot-all-cards/Cards/AllCards.cs | 0 .../Controllers/BotController.cs | 0 .../Teams/bot-all-cards/Dialogs/MainDialog.cs | 0 .../Teams/bot-all-cards/Images/1.Install.png | Bin .../Images/10.CollectionCard.png | Bin .../bot-all-cards/Images/11.ConnectorCard.png | Bin .../Teams/bot-all-cards/Images/2.Welcome.png | Bin .../bot-all-cards/Images/3.SelectCards.png | Bin .../bot-all-cards/Images/4.AdaptiveCard.png | Bin .../Teams/bot-all-cards/Images/5.HeroCard.png | Bin .../Teams/bot-all-cards/Images/6.OathCard.png | Bin .../bot-all-cards/Images/7.SignInCard.png | Bin .../bot-all-cards/Images/8.ThumbnailCard.png | Bin .../Teams/bot-all-cards/Images/9.ListCard.png | Bin .../Images/AdaptiveCardMedia.png | Bin .../Images/AdaptiveCardMedia2.png | Bin .../bot-all-cards/Images/Authentication.png | Bin .../bot-all-cards/Images/OauthConnection.png | Bin .../bot-all-cards/Images/allBotCardsGif.gif | Bin .../Teams/bot-all-cards/Program.cs | 0 .../Teams/bot-all-cards/README.md | 0 .../bot-all-cards/Resources/adaptiveCard.json | 0 .../Resources/adaptiveCardMedia.json | 0 .../Resources/collectionsCard.json | 0 .../bot-all-cards/Resources/heroCard.json | 0 .../bot-all-cards/Resources/listCard.json | 0 .../Resources/o365ConnectorCard.json | 0 .../Resources/thumbnailCard.json | 0 .../Teams/bot-all-cards/appManifest/color.png | Bin .../bot-all-cards/appManifest/manifest.json | 0 .../bot-all-cards/appManifest/outline.png | Bin .../Teams/bot-all-cards/appsettings.json | 0 .../Teams/bot-all-cards/wwwroot/default.htm | 0 .../BotConversationSsoQuickstart.csproj | 0 .../Bots/DialogBot.cs | 45 +++++ .../Bots/TeamsBot.cs | 40 +++++ .../Controllers/BotController.cs | 25 +++ .../Dialogs/LogoutDialog.cs | 95 ++++++++++ .../Dialogs/MainDialog.cs | 145 +++++++++++++++ .../Images/1.Install.png | Bin 0 -> 82591 bytes .../Images/2.Installed.png | Bin 0 -> 34152 bytes .../Images/3.Logged_In.png | Bin 0 -> 56759 bytes .../Images/4.Your_Token.png | Bin 0 -> 160063 bytes .../Images/BotConversationSsoQuickStart.gif | Bin 0 -> 212649 bytes .../Program.cs | 0 .../bot-conversation-sso-quickstart/README.md | 105 +++++++++++ .../SimpleGraphClient.cs | 131 ++++++++++++++ .../TeamsSSOAdapter.cs | 56 ++++++ .../appManifest/color.png | Bin .../appManifest/manifest.json | 20 +-- .../appManifest/outline.png | Bin .../appsettings.json | 38 ++++ .../Bots/ActivityBot.cs | 0 .../Cards/PersonalScopeCard.json | 0 .../Cards/TeamsScopeCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/PepolePickerAdaptiveCard.gif | Bin .../Images/Welcome.png | Bin .../Images/people-picker-card.png | Bin .../Images/people-picker-id.png | Bin .../Images/people-picker-info.png | Bin .../PeoplePicker.csproj | 0 .../Program.cs | 0 .../bot-people-picker-adaptive-card/README.md | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 .../wwwroot/default.htm | 0 .../Bots/ActivityBot.cs | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Installed.png | Bin .../Images/3.Interaction.png | Bin .../Images/4.1_and_2_Command_Interaction.png | Bin .../Images/5.Install_to_GC.png | Bin .../Images/6.Installed.png | Bin .../Images/7.1_and_2_Command_Interaction.png | Bin .../Bot_Channel_Messenging-RSC-nodejs-gif.gif | Bin .../Program.cs | 0 .../README.md | 0 .../ReceiveMessagesWithRSC.csproj | 0 .../appManifest/color.png | Bin .../appManifest/manifest.json | 0 .../appManifest/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 .../BotRequestApproval.csproj | 0 .../bot-request-approval/Bots/ActivityBot.cs | 0 .../Cards/ApprovedCard.json | 0 .../Cards/AssignedToCard.json | 0 .../Cards/CancelCard.json | 0 .../Cards/InitialCard.json | 0 .../Cards/OtherMembersCard.json | 0 .../Cards/RejectedCard.json | 0 .../Cards/RequestCard.json | 0 .../Cards/RequestDetailsCardForUser.json | 0 .../Controllers/BotController.cs | 0 .../Images/ApproveRejectCard.png | Bin .../Images/ApprovedRequest.png | Bin .../Images/CancelledRequest.png | Bin .../Images/CreateTask.png | Bin .../Images/EditCancelCard.png | Bin .../bot-request-approval/Images/EditTask.png | Bin .../Images/InitialCard.png | Bin .../Images/ManagerCard.png | Bin .../Images/OtherMemberCard.png | Bin .../bot-request-approval/Images/Preview.gif | Bin .../Images/RequestCard.png | Bin .../Images/StatusCard.png | Bin .../bot-request-approval/Images/UserCard.png | Bin .../Models/InitialSequentialCard.cs | 0 .../Models/RequestDetails.cs | 0 .../Teams/bot-request-approval/Program.cs | 0 .../Teams/bot-request-approval/README.md | 0 .../bot-request-approval/appPackage/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage/outline.png | Bin .../bot-request-approval/appsettings.json | 0 .../bot-request-approval/assets/sample.json | 0 .../Teams/bot-tag-mention/Bots/DialogBot.cs | 0 .../Bots/TeamsTagMentionBot.cs | 0 .../Controllers/BotController.cs | 0 .../bot-tag-mention/Dialogs/LogoutDialog.cs | 0 .../bot-tag-mention/Dialogs/MainDialog.cs | 0 .../Images/1.AddPersonalScope.png | Bin .../Images/2.LoginWithPersonalScope.png | Bin .../Images/3.AddToTeamsScope.png | Bin .../Images/4.WelcomeMessage_Teams.png | Bin .../Images/5.MetionedTag-2.png | Bin .../bot-tag-mention/Images/5.MetionedTag.png | Bin .../Images/6.TagMentionDetails.png | Bin .../Images/7.MessageWhenNoTagFound.png | Bin .../Images/8.WithOutCommand.png | Bin .../Images/Tag-mention-bot.gif | Bin .../Teams/bot-tag-mention/Program.cs | 0 .../Teams/bot-tag-mention/README.md | 0 .../Resources/UserMentionCardTemplate.json | 0 .../bot-tag-mention/SimpleGraphClient.cs | 0 .../bot-tag-mention/TagMentionBot.csproj | 0 .../appManifest/icon-color.png | Bin .../appManifest/icon-outline.png | Bin .../bot-tag-mention/appManifest/manifest.json | 0 .../Teams/bot-tag-mention/appsettings.json | 0 .../AppManifest/color.png | Bin .../AppManifest/manifest.json | 0 .../AppManifest/outline.png | Bin .../Bots/DialogBot.cs | 0 .../bot-teams-authentication/Bots/TeamsBot.cs | 0 .../Controllers/BotController.cs | 0 .../Dialogs/LogoutDialog.cs | 0 .../Dialogs/MainDialog.cs | 0 .../Images/1.Install.png | Bin .../Images/2.Welcome.png | Bin .../Images/3.AuthSuccess.png | Bin .../Images/4.AuthToken.png | Bin .../Images/5.Logout.png | Bin .../Images/auth-consent.png | Bin .../Images/bot-teams-auth.gif | Bin .../Teams/bot-teams-authentication/Program.cs | 0 .../Teams/bot-teams-authentication/README.md | 0 .../SimpleGraphClient.cs | 0 .../bot-teams-authentication/TeamsAuth.csproj | 0 .../bot-teams-authentication/appsettings.json | 0 .../assets/sample.json | 0 .../Bots/ActivityBot.cs | 0 .../Cards/DependentDropdown.json | 0 .../Cards/DynamicSearchCard.json | 0 .../Cards/StaticSearchCard.json | 0 .../Controllers/BotController.cs | 0 .../Images/1.Install.png | Bin .../Images/10.CountryOptions.png | Bin .../Images/11.CitiesAsPerTheCountry.png | Bin .../Images/12.SelectedDependantDropdown.png | Bin .../Images/2.Welcome.png | Bin .../Images/3.StaticSearch.png | Bin .../Images/4.StaticSearch2.png | Bin .../Images/5.SelectedOption.png | Bin .../Images/6.DynamicSearch.png | Bin .../Images/7.DynamicSearch2.png | Bin .../Images/8.SelectedDynamicSearch.png | Bin .../Images/9.DependantDropdown.png | Bin .../Images/TypedSearchModule.gif | Bin .../Models/InitialSequentialCard.cs | 0 .../Program.cs | 0 .../README.md | 0 .../TypeaheadSearch.csproj | 0 .../appPackage/color.png | Bin .../appPackage/manifest.json | 0 .../appPackage/outline.png | Bin .../appsettings.json | 0 .../assets/sample.json | 0 431 files changed, 1450 insertions(+), 47 deletions(-) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Bots/DialogBot.cs (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Bots/TeamsBot.cs (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Dialogs/MainDialog.cs (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Images/1.Install.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Images/2.Installed.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Images/3.Logged_In.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Images/4.Your_Token.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/Images/BotConversationSsoQuickStart.gif (100%) create mode 100644 src/samples/Compat/TeamsConversationSsoQuickstart/Program.cs rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/README.md (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/SimpleGraphClient.cs (100%) create mode 100644 src/samples/Compat/TeamsConversationSsoQuickstart/TeamsConversationSsoQuickstart.csproj rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/TeamsSSOAdapter.cs (100%) rename src/samples/Compat/{AuthenticationBotCompat => TeamsConversationSsoQuickstart}/appManifest/color.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/appManifest/manifest.json (100%) rename src/samples/Compat/{AuthenticationBotCompat => TeamsConversationSsoQuickstart}/appManifest/outline.png (100%) rename src/samples/{ToMigrate/Teams/bot-conversation-sso-quickstart => Compat/TeamsConversationSsoQuickstart}/appsettings.json (100%) create mode 100644 src/samples/test-bots/AdaptiveCardsBot/AdaptiveCardsBot.csproj create mode 100644 src/samples/test-bots/AdaptiveCardsBot/BotController.cs create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Model/AdaptiveCardSubmitData.cs create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Model/Package.cs create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Program.cs create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Properties/launchSettings.TEMPLATE.json create mode 100644 src/samples/test-bots/AdaptiveCardsBot/README.md create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Resources/DynamicSearchCard.json create mode 100644 src/samples/test-bots/AdaptiveCardsBot/Resources/StaticSearchCard.json create mode 100644 src/samples/test-bots/AdaptiveCardsBot/TypeAheadBot.cs create mode 100644 src/samples/test-bots/AdaptiveCardsBot/appManifest/color.png create mode 100644 src/samples/test-bots/AdaptiveCardsBot/appManifest/manifest.json create mode 100644 src/samples/test-bots/AdaptiveCardsBot/appManifest/outline.png create mode 100644 src/samples/test-bots/AdaptiveCardsBot/appsettings.json create mode 100644 src/samples/test-bots/AdaptiveCardsBot/assets/TypeaheadSearch.png rename src/samples/{ToMigrate => test-bots}/BotToBot/.gitignore (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/.config/dotnet-tools.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Bot1.csproj (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Bots/Bot1.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Controllers/Bot2ResponseController.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/Properties/launchSettings.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/README.md (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot1/wwwroot/default.htm (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/Bot2.csproj (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/BotAdapterWithErrorHandler.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/Bots/Bot2.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/Properties/launchSettings.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/README.md (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/wwwroot/default.htm (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/README.md (100%) rename src/samples/{ToMigrate => test-bots}/BotToBot/SimpleBotToBot.sln (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Cards/SuggestedActions.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/3.Red.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/4.Green.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/5.Blue.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/6.CardActions.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/appManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/appManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/AdaptiveCardActions/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/Bots/TeamsConversationBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/ConversationBot.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/appManifest/icon-color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/appManifest/icon-outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/ConversationBot/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/AppManifest/icon-color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/AppManifest/icon-outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/AppManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/Add-App.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/Link-Unfurling.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/OpenAppIcon.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/OpenNewMail.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/SearchInExtension.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/LinkUnfurling.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/LinkUnfurling/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/1.setup.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/2.add_to_meeting.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/3.tab_configuration.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/4.tab_context_details.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/AddToChat.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/Meeting-Details.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/MeetingContext.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/Participant-Details.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/ParticipantContext.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/Tab-View.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/meetingTabContext.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/MeetingContextApp.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/appPackage/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/appPackage/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/appPackage/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meeting-Context-App/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Cards/AgendaCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Cards/QuestionTemplate.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/2.Home_Page.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/4.Option_Card.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/5.Output_in_Chat.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/7.Popup_Window.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Images/MeetingNotification.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/ActionBase.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/AgendaItem.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/MeetingAgenda.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/MeetingNotification.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/ParticipantDetail.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/PushAgendaAction.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Models/SubmitFeedback.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Pages/_ViewStart.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/Titles.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/appPackage/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/appPackage/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/appPackage/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/Meetings-Notification/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Images/msgext-search.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/Resources/connectorCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/appManifest/icon-color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/MessagingExtensionsSearch/wwwroot/default.html (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Helper/AdaptiveCardHelper.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Helper/ApplicationSettings.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Helper/DeeplinkHelper.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Helper/UIConstants.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/1.InstallApp.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/10.GroupChat.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/11.GroupDialogs.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/12.GroupCustomForm.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/13.GroupResults.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/2.Dialogs.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/3.AdaptiveCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/5.CustomForm.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/6.Results.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/7.YouTube.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/8.Task.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/9.PowerApp.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/CardTaskFetchValue.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/TaskModuleIds.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/TaskModuleResponseFactory.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/TaskModuleUIConstants.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Models/UISettings.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/Configure.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/CustomForm.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/CustomForm.cshtml.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/HelloWorld.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/PowerApp.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/Shared/_Layout.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/Tasks.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/YouTube.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Pages/_ViewStart.cshtml (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/Resources/adaptiveCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/TaskModule.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/appManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/appManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/assets/sample.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/wwwroot/css/Site.css (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/wwwroot/css/custom.css (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/wwwroot/css/msteams-16.css (100%) rename src/samples/{ToMigrate => test-bots}/Teams/TaskModule/wwwroot/default.htm (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/BotAllCards.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Bots/DialogBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Bots/TeamsBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Cards/AllCards.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Dialogs/MainDialog.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/10.CollectionCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/11.ConnectorCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/2.Welcome.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/3.SelectCards.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/4.AdaptiveCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/5.HeroCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/6.OathCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/7.SignInCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/8.ThumbnailCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/9.ListCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/AdaptiveCardMedia.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/Authentication.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/OauthConnection.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Images/allBotCardsGif.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/adaptiveCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/adaptiveCardMedia.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/collectionsCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/heroCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/listCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/o365ConnectorCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/Resources/thumbnailCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/appManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/appManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-all-cards/wwwroot/default.htm (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj (100%) create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/1.Install.png create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif rename src/samples/{ToMigrate => test-bots}/Teams/bot-conversation-sso-quickstart/Program.cs (100%) create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/README.md create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs rename src/samples/{ToMigrate => test-bots}/Teams/bot-conversation-sso-quickstart/appManifest/color.png (100%) rename src/samples/{Compat/AuthenticationBotCompat => test-bots/Teams/bot-conversation-sso-quickstart}/appManifest/manifest.json (62%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-conversation-sso-quickstart/appManifest/outline.png (100%) create mode 100644 src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appsettings.json rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Images/Welcome.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/appManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/appManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/assets/sample.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-receive-channel-messages-withRSC/assets/sample.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/BotRequestApproval.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Bots/ActivityBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/ApprovedCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/AssignedToCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/CancelCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/InitialCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/OtherMembersCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/RejectedCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/RequestCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/ApproveRejectCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/ApprovedRequest.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/CancelledRequest.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/CreateTask.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/EditCancelCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/EditTask.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/InitialCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/ManagerCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/OtherMemberCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/Preview.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/RequestCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/StatusCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Images/UserCard.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Models/InitialSequentialCard.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Models/RequestDetails.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/appPackage/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/appPackage/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/appPackage/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-request-approval/assets/sample.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Bots/DialogBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Dialogs/MainDialog.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/1.AddPersonalScope.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/5.MetionedTag-2.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/5.MetionedTag.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/6.TagMentionDetails.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/8.WithOutCommand.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Images/Tag-mention-bot.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/SimpleGraphClient.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/TagMentionBot.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/appManifest/icon-color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/appManifest/icon-outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/appManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-tag-mention/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/AppManifest/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/AppManifest/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/AppManifest/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Bots/DialogBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Bots/TeamsBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Dialogs/MainDialog.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/2.Welcome.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/3.AuthSuccess.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/4.AuthToken.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/5.Logout.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/auth-consent.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Images/bot-teams-auth.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/SimpleGraphClient.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/TeamsAuth.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-teams-authentication/assets/sample.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/Program.cs (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/README.md (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json (100%) rename src/samples/{ToMigrate => test-bots}/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json (100%) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 2a56784e..9f7c1356 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -112,6 +112,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvalClient", "samples\Copil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBotCompat", "samples\Compat\AuthenticationBotCompat\AuthenticationBotCompat.csproj", "{B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsConversationSsoQuickstart", "samples\Compat\TeamsConversationSsoQuickstart\TeamsConversationSsoQuickstart.csproj", "{B27560C2-0125-4775-807E-DA2F2E5D4B1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -274,6 +276,10 @@ Global {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Debug|Any CPU.Build.0 = Debug|Any CPU {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Release|Any CPU.ActiveCfg = Release|Any CPU {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65}.Release|Any CPU.Build.0 = Release|Any CPU + {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -329,6 +335,7 @@ Global {CA58CF4C-2D7E-49CE-974C-9939321B1612} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65} = {36494671-1A2D-47F9-B53D-354E0690DA82} + {B27560C2-0125-4775-807E-DA2F2E5D4B1C} = {36494671-1A2D-47F9-B53D-354E0690DA82} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs index 3a7575c0..23f3121e 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/ProxyChannelApiHandler.cs @@ -19,7 +19,8 @@ namespace Microsoft.Agents.BotBuilder { /// /// This IChannelApiHandler is primarily used when calling another bot using DeliveryModes.Normal, and forwarding most - /// bot replies to the originating channel. + /// bot replies to the originating channel. This is the legacy behavior for the Root bot in a Skill scenario, including + /// for Dialogs SkillDialog. /// public class ProxyChannelApiHandler : IChannelApiHandler { diff --git a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs index 5b5c4346..49bff193 100644 --- a/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs +++ b/src/libraries/Hosting/AspNetCore/ServiceCollectionExtensions.cs @@ -17,56 +17,32 @@ namespace Microsoft.Agents.Hosting.AspNetCore { public static class ServiceCollectionExtensions { - /// - /// Add the default CloudAdapter. - /// - /// - /// - public static void AddCloudAdapter(this IServiceCollection services) + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory, IStorage storage = null) { - services.AddCloudAdapter(); + return AddBot(builder, implementationFactory, storage); } - /// - /// Add the derived CloudAdapter. - /// - /// - /// - public static void AddCloudAdapter(this IServiceCollection services) where T : CloudAdapter - { - AddAsyncCloudAdapterSupport(services); - - services.AddSingleton(); - services.AddSingleton(sp => sp.GetService()); - services.AddSingleton(sp => sp.GetService()); - } - - public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory) - { - return AddBot(builder, implementationFactory); - } - - public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory) + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, Func implementationFactory, IStorage storage = null) where TAdapter : CloudAdapter { - AddCore(builder); + AddCore(builder, storage); builder.Services.AddTransient(implementationFactory); return builder; } - public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, IStorage storage = null) where TBot : class, IBot { - return AddBot(builder); + return AddBot(builder, storage); } - public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder) + public static IHostApplicationBuilder AddBot(this IHostApplicationBuilder builder, IStorage storage = null) where TBot : class, IBot where TAdapter : CloudAdapter { - AddCore(builder); + AddCore(builder, storage); // Add the Bot, this is the primary worker for the bot. builder.Services.AddTransient(); @@ -74,7 +50,15 @@ public static IHostApplicationBuilder AddBot(this IHostApplicati return builder; } - public static IHostApplicationBuilder AddChannelHost(this IHostApplicationBuilder builder, string httpBotClientName = "HttpBotClient") + /// + /// Adds bot-to-bot functionality. + /// + /// + /// + /// + /// Used for ConversationIdFactory. If null, the registered IStorage will be used. + /// + public static IHostApplicationBuilder AddChannelHost(this IHostApplicationBuilder builder, string httpBotClientName = "HttpBotClient", IStorage storage = null) where THandler : class, IChannelApiHandler { ArgumentException.ThrowIfNullOrWhiteSpace(httpBotClientName); @@ -99,7 +83,14 @@ public static IHostApplicationBuilder AddChannelHost(this IHostApplica // Add conversation id factory. // This is a memory only implementation, and for production would require persistence. - builder.Services.AddSingleton(); + if (storage != null) + { + builder.Services.AddSingleton(sp => new ConversationIdFactory(storage)); + } + else + { + builder.Services.AddSingleton(); + } // Add bot callback handler. // This is the object that handles callback endpoints for bot responses. @@ -109,7 +100,31 @@ public static IHostApplicationBuilder AddChannelHost(this IHostApplica return builder; } - private static void AddCore(this IHostApplicationBuilder builder) + /// + /// Add the default CloudAdapter. + /// + /// + /// + public static void AddCloudAdapter(this IServiceCollection services) + { + services.AddCloudAdapter(); + } + + /// + /// Add the derived CloudAdapter. + /// + /// + /// + public static void AddCloudAdapter(this IServiceCollection services) where T : CloudAdapter + { + AddAsyncCloudAdapterSupport(services); + + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService()); + services.AddSingleton(sp => sp.GetService()); + } + + private static void AddCore(this IHostApplicationBuilder builder, IStorage storage = null) where TAdapter : CloudAdapter { // Add Connections object to access configured token connections. @@ -119,7 +134,14 @@ private static void AddCore(this IHostApplicationBuilder builder) builder.Services.AddSingleton(); // Add IStorage for turn state persistence - builder.Services.AddSingleton(); + if (storage != null) + { + builder.Services.AddSingleton(storage); + } + else + { + builder.Services.AddSingleton(); + } // Add the ChannelAdapter, this is the default adapter that works with Azure Bot Service and Activity Protocol. AddCloudAdapter(builder.Services); diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Bots/DialogBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/Bots/DialogBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Bots/TeamsBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/Bots/TeamsBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/Dialogs/LogoutDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/Dialogs/MainDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/1.Install.png b/src/samples/Compat/TeamsConversationSsoQuickstart/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/1.Install.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png b/src/samples/Compat/TeamsConversationSsoQuickstart/Images/2.Installed.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/2.Installed.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/Images/2.Installed.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png b/src/samples/Compat/TeamsConversationSsoQuickstart/Images/3.Logged_In.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/3.Logged_In.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/Images/3.Logged_In.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png b/src/samples/Compat/TeamsConversationSsoQuickstart/Images/4.Your_Token.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/4.Your_Token.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/Images/4.Your_Token.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif b/src/samples/Compat/TeamsConversationSsoQuickstart/Images/BotConversationSsoQuickStart.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif rename to src/samples/Compat/TeamsConversationSsoQuickstart/Images/BotConversationSsoQuickStart.gif diff --git a/src/samples/Compat/TeamsConversationSsoQuickstart/Program.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/Program.cs new file mode 100644 index 00000000..0f399128 --- /dev/null +++ b/src/samples/Compat/TeamsConversationSsoQuickstart/Program.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using BotConversationSsoQuickstart; +using BotConversationSsoQuickstart.Bots; +using BotConversationSsoQuickstart.Dialogs; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Storage; +using Microsoft.Agents.Samples; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Compat; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); + +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add basic bot functionality +builder.AddBot, TeamsSSOAdapter>(); + +// Create the Conversation state. +builder.Services.AddSingleton(); + +builder.Services.AddSingleton((sp) => +{ + return + [ + new AutoSaveStateMiddleware(true, sp.GetService()), + new TeamsSSOTokenExchangeMiddleware(sp.GetService(), builder.Configuration["ConnectionName"]) + ]; +}); + +// The Dialog that will be run by the bot. +builder.Services.AddSingleton(); + +var app = builder.Build(); + + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} + +app.Run(); diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/README.md b/src/samples/Compat/TeamsConversationSsoQuickstart/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/README.md rename to src/samples/Compat/TeamsConversationSsoQuickstart/README.md diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/SimpleGraphClient.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/SimpleGraphClient.cs diff --git a/src/samples/Compat/TeamsConversationSsoQuickstart/TeamsConversationSsoQuickstart.csproj b/src/samples/Compat/TeamsConversationSsoQuickstart/TeamsConversationSsoQuickstart.csproj new file mode 100644 index 00000000..dcdbee5d --- /dev/null +++ b/src/samples/Compat/TeamsConversationSsoQuickstart/TeamsConversationSsoQuickstart.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + latest + + + + + + + + + + + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs b/src/samples/Compat/TeamsConversationSsoQuickstart/TeamsSSOAdapter.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs rename to src/samples/Compat/TeamsConversationSsoQuickstart/TeamsSSOAdapter.cs diff --git a/src/samples/Compat/AuthenticationBotCompat/appManifest/color.png b/src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/color.png similarity index 100% rename from src/samples/Compat/AuthenticationBotCompat/appManifest/color.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json b/src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json rename to src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/manifest.json diff --git a/src/samples/Compat/AuthenticationBotCompat/appManifest/outline.png b/src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/outline.png similarity index 100% rename from src/samples/Compat/AuthenticationBotCompat/appManifest/outline.png rename to src/samples/Compat/TeamsConversationSsoQuickstart/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appsettings.json b/src/samples/Compat/TeamsConversationSsoQuickstart/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appsettings.json rename to src/samples/Compat/TeamsConversationSsoQuickstart/appsettings.json diff --git a/src/samples/test-bots/AdaptiveCardsBot/AdaptiveCardsBot.csproj b/src/samples/test-bots/AdaptiveCardsBot/AdaptiveCardsBot.csproj new file mode 100644 index 00000000..55acb2aa --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/AdaptiveCardsBot.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + latest + disable + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/samples/test-bots/AdaptiveCardsBot/BotController.cs b/src/samples/test-bots/AdaptiveCardsBot/BotController.cs new file mode 100644 index 00000000..80b5ab67 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace AdaptiveCards +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/Model/AdaptiveCardSubmitData.cs b/src/samples/test-bots/AdaptiveCardsBot/Model/AdaptiveCardSubmitData.cs new file mode 100644 index 00000000..e1d14381 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Model/AdaptiveCardSubmitData.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +namespace AdaptiveCardsBot.Model +{ + public class AdaptiveCardSubmitData + { + public string Verb { get; set; } + + public string ChoiceSelect { get; set; } + } +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/Model/Package.cs b/src/samples/test-bots/AdaptiveCardsBot/Model/Package.cs new file mode 100644 index 00000000..2da89423 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Model/Package.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace AdaptiveCardsBot.Model +{ + /// + /// The strongly typed NuGet package search result + /// + public class Package + { + public string Id { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/Program.cs b/src/samples/test-bots/AdaptiveCardsBot/Program.cs new file mode 100644 index 00000000..b475774a --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AdaptiveCardsBot; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add ApplicationOptions +builder.Services.AddTransient(sp => +{ + return new AgentApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; +}); + +// Add the bot (which is transient) +builder.AddBot(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/src/samples/test-bots/AdaptiveCardsBot/Properties/launchSettings.TEMPLATE.json b/src/samples/test-bots/AdaptiveCardsBot/Properties/launchSettings.TEMPLATE.json new file mode 100644 index 00000000..38bfefde --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Properties/launchSettings.TEMPLATE.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "EchoBot": { + "commandName": "Project", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "Connections__BotServiceConnection__Settings__TenantId": "<>", + "Connections__BotServiceConnection__Settings__ClientId": "<>", + "Connections__BotServiceConnection__Settings__AuthorityEndpoint": "https://login.microsoftonline.com/<>", + "Connections__BotServiceConnection__Settings__ClientSecret": "<>" + }, + "applicationUrl": "https://localhost:65284;http://localhost:3978" + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/AdaptiveCardsBot/README.md b/src/samples/test-bots/AdaptiveCardsBot/README.md new file mode 100644 index 00000000..a67624e6 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/README.md @@ -0,0 +1,40 @@ +# Typeahead Bot + +This sample shows how to incorporate the typeahead search functionality in Adaptive Cards into a Microsoft Teams application using [Bot Framework](https://dev.botframework.com) and the Teams AI SDK. Users can search nuget.org for packages. + +## Set up instructions + +All the samples in the C# .NET SDK can be set up in the same way. You can find the step by step instructions here: [Setup Instructions](../README.md). + +## Interacting with the bot + +![Typeahead search](./assets/TypeaheadSearch.png) + +Send "static" to get the Adaptive Card with static typeahead search control and send "dynamic" to get the Adaptive Card with dynamic typeahead search control. + +**static search**: Static typeahead search allows users to search from values specified within `Input.ChoiceSet` in the Adaptive Card payload. + +**dynamic search**: Dynamic typeahead search is useful to search and select data from large data sets. The data sets are loaded dynamically from the `dataset` specified in the Adaptive Card payload. + +On clicking "Submit" button, the bot will return the choices that have been selected. + +## Prerequisites + +- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 +- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) +- [Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) for Testing Web Chat. + +## Running this sample + +**To run the sample connected to Azure Bot Service, the following additional tools are required:** + +- Access to an Azure Subscription with access to preform the following tasks: + - Create and configure Entra ID Application Identities + - Create and configure an [Azure Bot Service](https://aka.ms/AgentsSDK-CreateBot) for your bot + - Create and configure an [Azure App Service](https://learn.microsoft.com/azure/app-service/) to deploy your bot on to. + - A tunneling tool to allow for local development and debugging should you wish to do local development whilst connected to a external client such as Microsoft Teams. + +## Further reading + +- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) +- [How Microsoft Teams bots work](https://learn.microsoft.com/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) diff --git a/src/samples/test-bots/AdaptiveCardsBot/Resources/DynamicSearchCard.json b/src/samples/test-bots/AdaptiveCardsBot/Resources/DynamicSearchCard.json new file mode 100644 index 00000000..578cb615 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Resources/DynamicSearchCard.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.3", + "type": "AdaptiveCard", + "body": [ + { + "text": "Select from the list, or start typing a package name.", + "wrap": true, + "type": "TextBlock" + }, + { + "columns": [ + { + "width": "stretch", + "items": [ + { + "choices": [ + { + "title": "Microsoft.Agents.Core", + "value": "static_option_1" + }, + { + "title": "Microsoft.Agents.BotBuilder", + "value": "static_option_2" + }, + { + "title": "Microsoft.Agents.CopilotStudio.Client", + "value": "static_option_3" + } + ], + "choices.data": { + "type": "Data.Query", + "dataset": "nugetpackages" + }, + "id": "choiceSelect", + "type": "Input.ChoiceSet", + "placeholder": "Package name", + "label": "Nuget package search", + "isRequired": true, + "errorMessage": "There was an error", + "isMultiSelect": true, + "style": "filtered" + } + ], + "type": "Column" + } + ], + "type": "ColumnSet" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Submit", + "data": { + "verb": "DynamicSubmit" + } + } + ] +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/Resources/StaticSearchCard.json b/src/samples/test-bots/AdaptiveCardsBot/Resources/StaticSearchCard.json new file mode 100644 index 00000000..26c5bd11 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/Resources/StaticSearchCard.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.3", + "type": "AdaptiveCard", + "body": [ + { + "text": "Select an IDE from the list.", + "wrap": true, + "type": "TextBlock" + }, + { + "columns": [ + { + "width": "auto", + "items": [ + { + "text": "IDE: ", + "wrap": true, + "height": "stretch", + "type": "TextBlock" + } + ], + "type": "Column" + } + ], + "type": "ColumnSet" + }, + { + "columns": [ + { + "width": "stretch", + "items": [ + { + "choices": [ + { + "title": "Visual studio", + "value": "visual_studio" + }, + { + "title": "IntelliJ IDEA ", + "value": "intelliJ_IDEA " + }, + { + "title": "Aptana Studio 3", + "value": "aptana_studio_3" + }, + { + "title": "PyCharm", + "value": "pycharm" + }, + { + "title": "PhpStorm", + "value": "phpstorm" + }, + { + "title": "WebStorm", + "value": "webstorm" + }, + { + "title": "NetBeans", + "value": "netbeans" + }, + { + "title": "Eclipse", + "value": "eclipse" + }, + { + "title": "RubyMine ", + "value": "rubymine " + }, + { + "title": "Visual studio code", + "value": "visual_studio_code" + } + ], + "id": "choiceSelect", + "placeholder": "Search for an IDE", + "style": "filtered", + "type": "Input.ChoiceSet" + } + ], + "type": "Column" + } + ], + "type": "ColumnSet" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Submit", + "data": { + "verb": "StaticSubmit" + } + } + ] +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/TypeAheadBot.cs b/src/samples/test-bots/AdaptiveCardsBot/TypeAheadBot.cs new file mode 100644 index 00000000..e764c8bd --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/TypeAheadBot.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AdaptiveCardsBot.Model; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.App.AdaptiveCards; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Agents.Core.Serialization; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Web; + +namespace AdaptiveCardsBot +{ + /// + /// Defines the activity handlers. + /// + public class TypeAheadBot : AgentApplication + { + private readonly string _staticSearchCardFilePath = Path.Combine(".", "Resources", "StaticSearchCard.json"); + private readonly string _dynamicSearchCardFilePath = Path.Combine(".", "Resources", "DynamicSearchCard.json"); + + private readonly HttpClient _httpClient; + + public TypeAheadBot(AgentApplicationOptions options, IHttpClientFactory httpClientFactory) : base(options) + { + _httpClient = httpClientFactory.CreateClient("WebClient"); + + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + OnMessage(new Regex(@"static", RegexOptions.IgnoreCase), StaticMessageHandlerAsync); + OnMessage(new Regex(@"dynamic", RegexOptions.IgnoreCase), DynamicMessageHandlerAsync); + + // Listen for query from dynamic search card + AdaptiveCards.OnSearch("nugetpackages", SearchHandlerAsync); + // Listen for submit buttons + AdaptiveCards.OnActionSubmit("StaticSubmit", StaticSubmitHandlerAsync); + AdaptiveCards.OnActionSubmit("DynamicSubmit", DynamicSubmitHandlerAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER HANDLERS + OnActivity(ActivityTypes.Message, MessageHandlerAsync); + } + + /// + /// Handles members added events. + /// + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync("Hello and welcome! With this sample you can see the functionality of static and dynamic search in adaptive card.", cancellationToken: cancellationToken); + await turnContext.SendActivityAsync("Send `static` or `dynamic`", cancellationToken: cancellationToken); + } + } + } + + /// + /// Handles "static" message. + /// + protected async Task StaticMessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + Attachment attachment = CreateAdaptiveCardAttachment(_staticSearchCardFilePath); + await turnContext.SendActivityAsync(MessageFactory.Attachment(attachment), cancellationToken); + } + + /// + /// Handles "dynamic" message. + /// + protected async Task DynamicMessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + Attachment attachment = CreateAdaptiveCardAttachment(_dynamicSearchCardFilePath); + await turnContext.SendActivityAsync(MessageFactory.Attachment(attachment), cancellationToken); + } + + /// + /// Handles messages except "static" and "dynamic". + /// + protected async Task MessageHandlerAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Try saying `static` or `dynamic`."), cancellationToken); + } + + /// + /// Handles Adaptive Card dynamic search events. + /// + protected async Task> SearchHandlerAsync(ITurnContext turnContext, ITurnState turnState, Query query, CancellationToken cancellationToken) + { + string queryText = query.Parameters.QueryText; + int count = query.Count; + + Package[] packages = await SearchPackages(queryText, count, cancellationToken); + IList searchResults = packages.Select(package => new AdaptiveCardsSearchResult(package.Id!, $"{package.Id} - {package.Description}")).ToList(); + + return searchResults; + } + + /// + /// Handles Adaptive Card Action.Submit events with verb "StaticSubmit". + /// + protected async Task StaticSubmitHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken) + { + AdaptiveCardSubmitData submitData = ProtocolJsonSerializer.ToObject(data); + await turnContext.SendActivityAsync(MessageFactory.Text($"Statically selected option is: {submitData!.ChoiceSelect}"), cancellationToken); + } + + /// + /// Handles Adaptive Card Action.Submit events with verb "DynamicSubmit". + /// + protected async Task DynamicSubmitHandlerAsync(ITurnContext turnContext, ITurnState turnState, object data, CancellationToken cancellationToken) + { + AdaptiveCardSubmitData submitData = ProtocolJsonSerializer.ToObject(data); + await turnContext.SendActivityAsync(MessageFactory.Text($"Dynamically selected option is: {submitData!.ChoiceSelect}"), cancellationToken); + } + + private async Task SearchPackages(string text, int size, CancellationToken cancellationToken) + { + // Call NuGet Search API + NameValueCollection query = HttpUtility.ParseQueryString(string.Empty); + query["q"] = text; + query["take"] = size.ToString(); + string queryString = query.ToString()!; + string responseContent; + try + { + responseContent = await _httpClient.GetStringAsync($"https://azuresearch-usnc.nuget.org/query?{queryString}", cancellationToken); + } + catch (Exception) + { + throw; + } + + if (!string.IsNullOrWhiteSpace(responseContent)) + { + var jobj = JsonObject.Parse(responseContent).AsObject(); + return jobj.ContainsKey("data") + ? ProtocolJsonSerializer.ToObject(jobj["data"]) + : []; + } + else + { + return Array.Empty(); + } + } + + private static Attachment CreateAdaptiveCardAttachment(string filePath) + { + var adaptiveCardJson = File.ReadAllText(filePath); + var adaptiveCardAttachment = new Attachment() + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = adaptiveCardJson + }; + return adaptiveCardAttachment; + } + } +} diff --git a/src/samples/test-bots/AdaptiveCardsBot/appManifest/color.png b/src/samples/test-bots/AdaptiveCardsBot/appManifest/color.png new file mode 100644 index 0000000000000000000000000000000000000000..f27ccf2036bf2264dc0d11edf2af2bda62e4efdf GIT binary patch literal 1066 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaloCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49xpIT^vIy7~kGC%#(I!aJU%yVD^#PnvNe5 zY1um`Oj+#WC3y9e;Us63+9EE_EXjNxu|}7}jyc6;|Ee3L%wi^d-gxKYxi>fe9)4Wc zxa_YvcME5O3F8DchD$6Cvlu*t88Vp^d>NL|Lr`DL?D4N}c{_jp%(HAg{rPUu*Szg# zj!7mc`&s@7H-CTs|F4$!|Ce$kF#Fm5;7{vuU}%<3{UCovtdW7u?A8PO8JbLtJXu!` z)*E=Uq<`n{|J}Oq&G+9=pRT^{A7^iE9sTdm-|J7arQcTAE#7O4&s)AbmEKG-&pNxS zC@bvpTt>gz>5tZEFHbWKWmwE(Czx~Ggt5o$h06xsU>1W{3Bm_|n8_b_(d@&LeEW~i zg^dTlUYFmmyZpo3^85CcxxEL@T$u4k9$$#sDCao5UUz!>`!fG+?(yZBb?@R92k)%- zop#eIy}>bd?)!h82_c(x{)!wp;MSY4tn@sS#GMSmGuvLoGe{eFu^7LrP;BV6C}r84 z_dQ80!}%J=HG4bxbez$5Ys+@0HQnxRMXMf1ieE3d1B&%|CHbgX^da?eHs6`1kLARhjOac zf3;y1Iq~M{LLS3g_M2bU{+PBvomV=FH7$YTy5I%1<5B$=?>3fqI5P%5iajq7)W9SX p;gpazd1JnvZNlx8HB0WjVJ`J~Q+P@%pA+aZ22WQ%mvv4FO#n^cR9FB2 literal 0 HcmV?d00001 diff --git a/src/samples/test-bots/AdaptiveCardsBot/appsettings.json b/src/samples/test-bots/AdaptiveCardsBot/appsettings.json new file mode 100644 index 00000000..8a57c873 --- /dev/null +++ b/src/samples/test-bots/AdaptiveCardsBot/appsettings.json @@ -0,0 +1,37 @@ +{ + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/AdaptiveCardsBot/assets/TypeaheadSearch.png b/src/samples/test-bots/AdaptiveCardsBot/assets/TypeaheadSearch.png new file mode 100644 index 0000000000000000000000000000000000000000..b203909a2e4b623290ab14bae582f3d0a23cd476 GIT binary patch literal 155464 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 a^UKF|B79KFPbU|^KUpay$$asbZ~hM@6J!Gb literal 0 HcmV?d00001 diff --git a/src/samples/ToMigrate/BotToBot/.gitignore b/src/samples/test-bots/BotToBot/.gitignore similarity index 100% rename from src/samples/ToMigrate/BotToBot/.gitignore rename to src/samples/test-bots/BotToBot/.gitignore diff --git a/src/samples/ToMigrate/BotToBot/Bot1/.config/dotnet-tools.json b/src/samples/test-bots/BotToBot/Bot1/.config/dotnet-tools.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/.config/dotnet-tools.json rename to src/samples/test-bots/BotToBot/Bot1/.config/dotnet-tools.json diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Bot1.csproj b/src/samples/test-bots/BotToBot/Bot1/Bot1.csproj similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Bot1.csproj rename to src/samples/test-bots/BotToBot/Bot1/Bot1.csproj diff --git a/src/samples/ToMigrate/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs b/src/samples/test-bots/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs rename to src/samples/test-bots/BotToBot/Bot1/BotHostAdapterWithErrorHandler.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Bots/Bot1.cs b/src/samples/test-bots/BotToBot/Bot1/Bots/Bot1.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Bots/Bot1.cs rename to src/samples/test-bots/BotToBot/Bot1/Bots/Bot1.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Controllers/Bot2ResponseController.cs b/src/samples/test-bots/BotToBot/Bot1/Controllers/Bot2ResponseController.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Controllers/Bot2ResponseController.cs rename to src/samples/test-bots/BotToBot/Bot1/Controllers/Bot2ResponseController.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Controllers/BotController.cs b/src/samples/test-bots/BotToBot/Bot1/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Controllers/BotController.cs rename to src/samples/test-bots/BotToBot/Bot1/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Program.cs b/src/samples/test-bots/BotToBot/Bot1/Program.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Program.cs rename to src/samples/test-bots/BotToBot/Bot1/Program.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot1/Properties/launchSettings.json b/src/samples/test-bots/BotToBot/Bot1/Properties/launchSettings.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/Properties/launchSettings.json rename to src/samples/test-bots/BotToBot/Bot1/Properties/launchSettings.json diff --git a/src/samples/ToMigrate/BotToBot/Bot1/README.md b/src/samples/test-bots/BotToBot/Bot1/README.md similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/README.md rename to src/samples/test-bots/BotToBot/Bot1/README.md diff --git a/src/samples/ToMigrate/BotToBot/Bot1/appsettings.json b/src/samples/test-bots/BotToBot/Bot1/appsettings.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/appsettings.json rename to src/samples/test-bots/BotToBot/Bot1/appsettings.json diff --git a/src/samples/ToMigrate/BotToBot/Bot1/wwwroot/default.htm b/src/samples/test-bots/BotToBot/Bot1/wwwroot/default.htm similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot1/wwwroot/default.htm rename to src/samples/test-bots/BotToBot/Bot1/wwwroot/default.htm diff --git a/src/samples/ToMigrate/BotToBot/Bot2/Bot2.csproj b/src/samples/test-bots/BotToBot/Bot2/Bot2.csproj similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/Bot2.csproj rename to src/samples/test-bots/BotToBot/Bot2/Bot2.csproj diff --git a/src/samples/ToMigrate/BotToBot/Bot2/BotAdapterWithErrorHandler.cs b/src/samples/test-bots/BotToBot/Bot2/BotAdapterWithErrorHandler.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/BotAdapterWithErrorHandler.cs rename to src/samples/test-bots/BotToBot/Bot2/BotAdapterWithErrorHandler.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot2/Bots/Bot2.cs b/src/samples/test-bots/BotToBot/Bot2/Bots/Bot2.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/Bots/Bot2.cs rename to src/samples/test-bots/BotToBot/Bot2/Bots/Bot2.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot2/Controllers/BotController.cs b/src/samples/test-bots/BotToBot/Bot2/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/Controllers/BotController.cs rename to src/samples/test-bots/BotToBot/Bot2/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot2/Program.cs b/src/samples/test-bots/BotToBot/Bot2/Program.cs similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/Program.cs rename to src/samples/test-bots/BotToBot/Bot2/Program.cs diff --git a/src/samples/ToMigrate/BotToBot/Bot2/Properties/launchSettings.json b/src/samples/test-bots/BotToBot/Bot2/Properties/launchSettings.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/Properties/launchSettings.json rename to src/samples/test-bots/BotToBot/Bot2/Properties/launchSettings.json diff --git a/src/samples/ToMigrate/BotToBot/Bot2/README.md b/src/samples/test-bots/BotToBot/Bot2/README.md similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/README.md rename to src/samples/test-bots/BotToBot/Bot2/README.md diff --git a/src/samples/ToMigrate/BotToBot/Bot2/appsettings.json b/src/samples/test-bots/BotToBot/Bot2/appsettings.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/appsettings.json rename to src/samples/test-bots/BotToBot/Bot2/appsettings.json diff --git a/src/samples/ToMigrate/BotToBot/Bot2/wwwroot/default.htm b/src/samples/test-bots/BotToBot/Bot2/wwwroot/default.htm similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/wwwroot/default.htm rename to src/samples/test-bots/BotToBot/Bot2/wwwroot/default.htm diff --git a/src/samples/ToMigrate/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json b/src/samples/test-bots/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json similarity index 100% rename from src/samples/ToMigrate/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json rename to src/samples/test-bots/BotToBot/Bot2/wwwroot/manifest/echobot-manifest-1.0.json diff --git a/src/samples/ToMigrate/BotToBot/README.md b/src/samples/test-bots/BotToBot/README.md similarity index 100% rename from src/samples/ToMigrate/BotToBot/README.md rename to src/samples/test-bots/BotToBot/README.md diff --git a/src/samples/ToMigrate/BotToBot/SimpleBotToBot.sln b/src/samples/test-bots/BotToBot/SimpleBotToBot.sln similarity index 100% rename from src/samples/ToMigrate/BotToBot/SimpleBotToBot.sln rename to src/samples/test-bots/BotToBot/SimpleBotToBot.sln diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj b/src/samples/test-bots/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj rename to src/samples/test-bots/Teams/AdaptiveCardActions/AdaptiveCardActions.csproj diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs b/src/samples/test-bots/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs rename to src/samples/test-bots/Teams/AdaptiveCardActions/Bots/AdaptiveCardActionsBot.cs diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json b/src/samples/test-bots/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json rename to src/samples/test-bots/Teams/AdaptiveCardActions/Cards/AdaptiveCardActions.json diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/SuggestedActions.json b/src/samples/test-bots/Teams/AdaptiveCardActions/Cards/SuggestedActions.json similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/SuggestedActions.json rename to src/samples/test-bots/Teams/AdaptiveCardActions/Cards/SuggestedActions.json diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json b/src/samples/test-bots/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json rename to src/samples/test-bots/Teams/AdaptiveCardActions/Cards/ToggleVisibleCard.json diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Controllers/BotController.cs b/src/samples/test-bots/Teams/AdaptiveCardActions/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Controllers/BotController.cs rename to src/samples/test-bots/Teams/AdaptiveCardActions/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/1.Install.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/1.Install.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/10.ToggleVisibiliyCard.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/11.VisibleOnClick.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/2.WelcomeMessage.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/3.Red.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/3.Red.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/3.Red.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/3.Red.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/4.Green.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/4.Green.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/4.Green.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/4.Green.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/5.Blue.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/5.Blue.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/5.Blue.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/5.Blue.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/6.CardActions.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/6.CardActions.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/6.CardActions.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/6.CardActions.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/7.ActionSubmit.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/8.ActionShowCard.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif b/src/samples/test-bots/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif rename to src/samples/test-bots/Teams/AdaptiveCardActions/Images/AdaptiveCardActions.gif diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/Program.cs b/src/samples/test-bots/Teams/AdaptiveCardActions/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/Program.cs rename to src/samples/test-bots/Teams/AdaptiveCardActions/Program.cs diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/README.md b/src/samples/test-bots/Teams/AdaptiveCardActions/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/README.md rename to src/samples/test-bots/Teams/AdaptiveCardActions/README.md diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/color.png b/src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/color.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/manifest.json b/src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/manifest.json rename to src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/outline.png b/src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/appManifest/outline.png rename to src/samples/test-bots/Teams/AdaptiveCardActions/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/AdaptiveCardActions/appsettings.json b/src/samples/test-bots/Teams/AdaptiveCardActions/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/AdaptiveCardActions/appsettings.json rename to src/samples/test-bots/Teams/AdaptiveCardActions/appsettings.json diff --git a/src/samples/ToMigrate/Teams/ConversationBot/Bots/TeamsConversationBot.cs b/src/samples/test-bots/Teams/ConversationBot/Bots/TeamsConversationBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/Bots/TeamsConversationBot.cs rename to src/samples/test-bots/Teams/ConversationBot/Bots/TeamsConversationBot.cs diff --git a/src/samples/ToMigrate/Teams/ConversationBot/Controllers/BotController.cs b/src/samples/test-bots/Teams/ConversationBot/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/Controllers/BotController.cs rename to src/samples/test-bots/Teams/ConversationBot/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/ConversationBot/ConversationBot.csproj b/src/samples/test-bots/Teams/ConversationBot/ConversationBot.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/ConversationBot.csproj rename to src/samples/test-bots/Teams/ConversationBot/ConversationBot.csproj diff --git a/src/samples/ToMigrate/Teams/ConversationBot/Program.cs b/src/samples/test-bots/Teams/ConversationBot/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/Program.cs rename to src/samples/test-bots/Teams/ConversationBot/Program.cs diff --git a/src/samples/ToMigrate/Teams/ConversationBot/README.md b/src/samples/test-bots/Teams/ConversationBot/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/README.md rename to src/samples/test-bots/Teams/ConversationBot/README.md diff --git a/src/samples/ToMigrate/Teams/ConversationBot/Resources/UserMentionCardTemplate.json b/src/samples/test-bots/Teams/ConversationBot/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/Resources/UserMentionCardTemplate.json rename to src/samples/test-bots/Teams/ConversationBot/Resources/UserMentionCardTemplate.json diff --git a/src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-color.png b/src/samples/test-bots/Teams/ConversationBot/appManifest/icon-color.png similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-color.png rename to src/samples/test-bots/Teams/ConversationBot/appManifest/icon-color.png diff --git a/src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-outline.png b/src/samples/test-bots/Teams/ConversationBot/appManifest/icon-outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/appManifest/icon-outline.png rename to src/samples/test-bots/Teams/ConversationBot/appManifest/icon-outline.png diff --git a/src/samples/ToMigrate/Teams/ConversationBot/appManifest/manifest.json b/src/samples/test-bots/Teams/ConversationBot/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/appManifest/manifest.json rename to src/samples/test-bots/Teams/ConversationBot/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/ConversationBot/appsettings.json b/src/samples/test-bots/Teams/ConversationBot/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/ConversationBot/appsettings.json rename to src/samples/test-bots/Teams/ConversationBot/appsettings.json diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-color.png b/src/samples/test-bots/Teams/LinkUnfurling/AppManifest/icon-color.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-color.png rename to src/samples/test-bots/Teams/LinkUnfurling/AppManifest/icon-color.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-outline.png b/src/samples/test-bots/Teams/LinkUnfurling/AppManifest/icon-outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/icon-outline.png rename to src/samples/test-bots/Teams/LinkUnfurling/AppManifest/icon-outline.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/manifest.json b/src/samples/test-bots/Teams/LinkUnfurling/AppManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/AppManifest/manifest.json rename to src/samples/test-bots/Teams/LinkUnfurling/AppManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs b/src/samples/test-bots/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs rename to src/samples/test-bots/Teams/LinkUnfurling/Bots/LinkUnfurlingBot.cs diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Controllers/BotController.cs b/src/samples/test-bots/Teams/LinkUnfurling/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Controllers/BotController.cs rename to src/samples/test-bots/Teams/LinkUnfurling/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/Add-App.png b/src/samples/test-bots/Teams/LinkUnfurling/Images/Add-App.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/Add-App.png rename to src/samples/test-bots/Teams/LinkUnfurling/Images/Add-App.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/Link-Unfurling.png b/src/samples/test-bots/Teams/LinkUnfurling/Images/Link-Unfurling.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/Link-Unfurling.png rename to src/samples/test-bots/Teams/LinkUnfurling/Images/Link-Unfurling.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenAppIcon.png b/src/samples/test-bots/Teams/LinkUnfurling/Images/OpenAppIcon.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenAppIcon.png rename to src/samples/test-bots/Teams/LinkUnfurling/Images/OpenAppIcon.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenNewMail.png b/src/samples/test-bots/Teams/LinkUnfurling/Images/OpenNewMail.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/OpenNewMail.png rename to src/samples/test-bots/Teams/LinkUnfurling/Images/OpenNewMail.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/SearchInExtension.png b/src/samples/test-bots/Teams/LinkUnfurling/Images/SearchInExtension.png similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/SearchInExtension.png rename to src/samples/test-bots/Teams/LinkUnfurling/Images/SearchInExtension.png diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif b/src/samples/test-bots/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif rename to src/samples/test-bots/Teams/LinkUnfurling/Images/msgext-link-unfurling.gif diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/LinkUnfurling.csproj b/src/samples/test-bots/Teams/LinkUnfurling/LinkUnfurling.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/LinkUnfurling.csproj rename to src/samples/test-bots/Teams/LinkUnfurling/LinkUnfurling.csproj diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/Program.cs b/src/samples/test-bots/Teams/LinkUnfurling/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/Program.cs rename to src/samples/test-bots/Teams/LinkUnfurling/Program.cs diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/README.md b/src/samples/test-bots/Teams/LinkUnfurling/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/README.md rename to src/samples/test-bots/Teams/LinkUnfurling/README.md diff --git a/src/samples/ToMigrate/Teams/LinkUnfurling/appsettings.json b/src/samples/test-bots/Teams/LinkUnfurling/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/LinkUnfurling/appsettings.json rename to src/samples/test-bots/Teams/LinkUnfurling/appsettings.json diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs b/src/samples/test-bots/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs rename to src/samples/test-bots/Teams/Meeting-Context-App/Bots/MeetingContextBot.cs diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Controllers/BotController.cs b/src/samples/test-bots/Teams/Meeting-Context-App/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Controllers/BotController.cs rename to src/samples/test-bots/Teams/Meeting-Context-App/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/1.setup.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/1.setup.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/1.setup.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/1.setup.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/2.add_to_meeting.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/2.add_to_meeting.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/2.add_to_meeting.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/2.add_to_meeting.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/3.tab_configuration.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/3.tab_configuration.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/3.tab_configuration.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/3.tab_configuration.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/4.tab_context_details.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/4.tab_context_details.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/4.tab_context_details.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/4.tab_context_details.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/AddToChat.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/AddToChat.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/AddToChat.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/AddToChat.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Meeting-Details.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/Meeting-Details.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Meeting-Details.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/Meeting-Details.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/MeetingContext.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/MeetingContext.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/MeetingContext.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/MeetingContext.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Participant-Details.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/Participant-Details.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Participant-Details.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/Participant-Details.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/ParticipantContext.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/ParticipantContext.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/ParticipantContext.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/ParticipantContext.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/Setup-Tab-Bot.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Tab-View.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/Tab-View.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/Tab-View.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/Tab-View.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meetingTabContext.png b/src/samples/test-bots/Teams/Meeting-Context-App/Images/meetingTabContext.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meetingTabContext.png rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/meetingTabContext.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif b/src/samples/test-bots/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif rename to src/samples/test-bots/Teams/Meeting-Context-App/Images/meeting_context_csharp.gif diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/MeetingContextApp.csproj b/src/samples/test-bots/Teams/Meeting-Context-App/MeetingContextApp.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/MeetingContextApp.csproj rename to src/samples/test-bots/Teams/Meeting-Context-App/MeetingContextApp.csproj diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/Program.cs b/src/samples/test-bots/Teams/Meeting-Context-App/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/Program.cs rename to src/samples/test-bots/Teams/Meeting-Context-App/Program.cs diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/README.md b/src/samples/test-bots/Teams/Meeting-Context-App/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/README.md rename to src/samples/test-bots/Teams/Meeting-Context-App/README.md diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/color.png b/src/samples/test-bots/Teams/Meeting-Context-App/appPackage/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/color.png rename to src/samples/test-bots/Teams/Meeting-Context-App/appPackage/color.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/manifest.json b/src/samples/test-bots/Teams/Meeting-Context-App/appPackage/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/manifest.json rename to src/samples/test-bots/Teams/Meeting-Context-App/appPackage/manifest.json diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/outline.png b/src/samples/test-bots/Teams/Meeting-Context-App/appPackage/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/appPackage/outline.png rename to src/samples/test-bots/Teams/Meeting-Context-App/appPackage/outline.png diff --git a/src/samples/ToMigrate/Teams/Meeting-Context-App/appsettings.json b/src/samples/test-bots/Teams/Meeting-Context-App/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meeting-Context-App/appsettings.json rename to src/samples/test-bots/Teams/Meeting-Context-App/appsettings.json diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs b/src/samples/test-bots/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Bots/InMeetingNotifications.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/AgendaCard.json b/src/samples/test-bots/Teams/Meetings-Notification/Cards/AgendaCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Cards/AgendaCard.json rename to src/samples/test-bots/Teams/Meetings-Notification/Cards/AgendaCard.json diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/QuestionTemplate.json b/src/samples/test-bots/Teams/Meetings-Notification/Cards/QuestionTemplate.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Cards/QuestionTemplate.json rename to src/samples/test-bots/Teams/Meetings-Notification/Cards/QuestionTemplate.json diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json b/src/samples/test-bots/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json rename to src/samples/test-bots/Teams/Meetings-Notification/Cards/SendTargetNotificationCard.json diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Controllers/BotController.cs b/src/samples/test-bots/Teams/Meetings-Notification/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Controllers/BotController.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/1.Install.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/1.Install.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/2.Home_Page.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/2.Home_Page.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/2.Home_Page.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/2.Home_Page.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/3.Send_Meeting_Notification.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/4.Option_Card.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/4.Option_Card.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/4.Option_Card.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/4.Option_Card.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/5.Output_in_Chat.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/5.Output_in_Chat.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/5.Output_in_Chat.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/5.Output_in_Chat.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/6.Card_in_Meeting_Chat.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/7.Popup_Window.png b/src/samples/test-bots/Teams/Meetings-Notification/Images/7.Popup_Window.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/7.Popup_Window.png rename to src/samples/test-bots/Teams/Meetings-Notification/Images/7.Popup_Window.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Images/MeetingNotification.gif b/src/samples/test-bots/Teams/Meetings-Notification/Images/MeetingNotification.gif similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Images/MeetingNotification.gif rename to src/samples/test-bots/Teams/Meetings-Notification/Images/MeetingNotification.gif diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj b/src/samples/test-bots/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj rename to src/samples/test-bots/Teams/Meetings-Notification/InMeetingNotificationsBot.csproj diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/ActionBase.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/ActionBase.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/ActionBase.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/ActionBase.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/AgendaItem.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/AgendaItem.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/AgendaItem.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/AgendaItem.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingAgenda.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/MeetingAgenda.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingAgenda.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/MeetingAgenda.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingNotification.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/MeetingNotification.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/MeetingNotification.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/MeetingNotification.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/ParticipantDetail.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/ParticipantDetail.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/ParticipantDetail.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/ParticipantDetail.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/PushAgendaAction.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/PushAgendaAction.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/PushAgendaAction.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/PushAgendaAction.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Models/SubmitFeedback.cs b/src/samples/test-bots/Teams/Meetings-Notification/Models/SubmitFeedback.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Models/SubmitFeedback.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Models/SubmitFeedback.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml b/src/samples/test-bots/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs b/src/samples/test-bots/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/InMeetingNotificationPage.cshtml.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml b/src/samples/test-bots/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs b/src/samples/test-bots/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/SendNotificationPage.cshtml.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml b/src/samples/test-bots/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/Shared/_Layout.cshtml diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Pages/_ViewStart.cshtml b/src/samples/test-bots/Teams/Meetings-Notification/Pages/_ViewStart.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Pages/_ViewStart.cshtml rename to src/samples/test-bots/Teams/Meetings-Notification/Pages/_ViewStart.cshtml diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Program.cs b/src/samples/test-bots/Teams/Meetings-Notification/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Program.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Program.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/README.md b/src/samples/test-bots/Teams/Meetings-Notification/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/README.md rename to src/samples/test-bots/Teams/Meetings-Notification/README.md diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/Titles.cs b/src/samples/test-bots/Teams/Meetings-Notification/Titles.cs similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/Titles.cs rename to src/samples/test-bots/Teams/Meetings-Notification/Titles.cs diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/color.png b/src/samples/test-bots/Teams/Meetings-Notification/appPackage/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/color.png rename to src/samples/test-bots/Teams/Meetings-Notification/appPackage/color.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/manifest.json b/src/samples/test-bots/Teams/Meetings-Notification/appPackage/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/manifest.json rename to src/samples/test-bots/Teams/Meetings-Notification/appPackage/manifest.json diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/outline.png b/src/samples/test-bots/Teams/Meetings-Notification/appPackage/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/appPackage/outline.png rename to src/samples/test-bots/Teams/Meetings-Notification/appPackage/outline.png diff --git a/src/samples/ToMigrate/Teams/Meetings-Notification/appsettings.json b/src/samples/test-bots/Teams/Meetings-Notification/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/Meetings-Notification/appsettings.json rename to src/samples/test-bots/Teams/Meetings-Notification/appsettings.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Bots/TeamsMessagingExtensionsSearchBot.cs diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Controllers/BotController.cs b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Controllers/BotController.cs rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/1.Install.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/1.Install.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/2.AddSuccessfully.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/3.SelectSample.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/4.QueryResults.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/5.SentSelectedPackage.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/6.SearchQueryResult-2.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/msgext-search.gif b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/msgext-search.gif similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Images/msgext-search.gif rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Images/msgext-search.gif diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj b/src/samples/test-bots/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/MessagingExtensionsSearch.csproj diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Program.cs b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Program.cs rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Program.cs diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/README.md b/src/samples/test-bots/Teams/MessagingExtensionsSearch/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/README.md rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/README.md diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/RestaurantCard.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/UserMentionCardTemplate.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/connectorCard.json b/src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/connectorCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/Resources/connectorCard.json rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/Resources/connectorCard.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-color.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/icon-color.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-color.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/icon-color.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png b/src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/icon-outline.png diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/manifest.json b/src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appManifest/manifest.json rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appsettings.json b/src/samples/test-bots/Teams/MessagingExtensionsSearch/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/appsettings.json rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/appsettings.json diff --git a/src/samples/ToMigrate/Teams/MessagingExtensionsSearch/wwwroot/default.html b/src/samples/test-bots/Teams/MessagingExtensionsSearch/wwwroot/default.html similarity index 100% rename from src/samples/ToMigrate/Teams/MessagingExtensionsSearch/wwwroot/default.html rename to src/samples/test-bots/Teams/MessagingExtensionsSearch/wwwroot/default.html diff --git a/src/samples/ToMigrate/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs b/src/samples/test-bots/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs rename to src/samples/test-bots/Teams/TaskModule/Bots/TeamsTaskModuleBot.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Controllers/BotController.cs b/src/samples/test-bots/Teams/TaskModule/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Controllers/BotController.cs rename to src/samples/test-bots/Teams/TaskModule/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Helper/AdaptiveCardHelper.cs b/src/samples/test-bots/Teams/TaskModule/Helper/AdaptiveCardHelper.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Helper/AdaptiveCardHelper.cs rename to src/samples/test-bots/Teams/TaskModule/Helper/AdaptiveCardHelper.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Helper/ApplicationSettings.cs b/src/samples/test-bots/Teams/TaskModule/Helper/ApplicationSettings.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Helper/ApplicationSettings.cs rename to src/samples/test-bots/Teams/TaskModule/Helper/ApplicationSettings.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Helper/DeeplinkHelper.cs b/src/samples/test-bots/Teams/TaskModule/Helper/DeeplinkHelper.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Helper/DeeplinkHelper.cs rename to src/samples/test-bots/Teams/TaskModule/Helper/DeeplinkHelper.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Helper/UIConstants.cs b/src/samples/test-bots/Teams/TaskModule/Helper/UIConstants.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Helper/UIConstants.cs rename to src/samples/test-bots/Teams/TaskModule/Helper/UIConstants.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/1.InstallApp.png b/src/samples/test-bots/Teams/TaskModule/Images/1.InstallApp.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/1.InstallApp.png rename to src/samples/test-bots/Teams/TaskModule/Images/1.InstallApp.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/10.GroupChat.png b/src/samples/test-bots/Teams/TaskModule/Images/10.GroupChat.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/10.GroupChat.png rename to src/samples/test-bots/Teams/TaskModule/Images/10.GroupChat.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/11.GroupDialogs.png b/src/samples/test-bots/Teams/TaskModule/Images/11.GroupDialogs.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/11.GroupDialogs.png rename to src/samples/test-bots/Teams/TaskModule/Images/11.GroupDialogs.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/12.GroupCustomForm.png b/src/samples/test-bots/Teams/TaskModule/Images/12.GroupCustomForm.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/12.GroupCustomForm.png rename to src/samples/test-bots/Teams/TaskModule/Images/12.GroupCustomForm.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/13.GroupResults.png b/src/samples/test-bots/Teams/TaskModule/Images/13.GroupResults.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/13.GroupResults.png rename to src/samples/test-bots/Teams/TaskModule/Images/13.GroupResults.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/2.Dialogs.png b/src/samples/test-bots/Teams/TaskModule/Images/2.Dialogs.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/2.Dialogs.png rename to src/samples/test-bots/Teams/TaskModule/Images/2.Dialogs.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/3.AdaptiveCard.png b/src/samples/test-bots/Teams/TaskModule/Images/3.AdaptiveCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/3.AdaptiveCard.png rename to src/samples/test-bots/Teams/TaskModule/Images/3.AdaptiveCard.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png b/src/samples/test-bots/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png rename to src/samples/test-bots/Teams/TaskModule/Images/4.ThanksAdaptiveCard.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/5.CustomForm.png b/src/samples/test-bots/Teams/TaskModule/Images/5.CustomForm.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/5.CustomForm.png rename to src/samples/test-bots/Teams/TaskModule/Images/5.CustomForm.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/6.Results.png b/src/samples/test-bots/Teams/TaskModule/Images/6.Results.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/6.Results.png rename to src/samples/test-bots/Teams/TaskModule/Images/6.Results.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/7.YouTube.png b/src/samples/test-bots/Teams/TaskModule/Images/7.YouTube.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/7.YouTube.png rename to src/samples/test-bots/Teams/TaskModule/Images/7.YouTube.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/8.Task.png b/src/samples/test-bots/Teams/TaskModule/Images/8.Task.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/8.Task.png rename to src/samples/test-bots/Teams/TaskModule/Images/8.Task.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/9.PowerApp.png b/src/samples/test-bots/Teams/TaskModule/Images/9.PowerApp.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/9.PowerApp.png rename to src/samples/test-bots/Teams/TaskModule/Images/9.PowerApp.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif b/src/samples/test-bots/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif rename to src/samples/test-bots/Teams/TaskModule/Images/Bot_Tab_TaskModule.gif diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs b/src/samples/test-bots/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs rename to src/samples/test-bots/Teams/TaskModule/Models/AdaptiveCardTaskFetchValue.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/CardTaskFetchValue.cs b/src/samples/test-bots/Teams/TaskModule/Models/CardTaskFetchValue.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/CardTaskFetchValue.cs rename to src/samples/test-bots/Teams/TaskModule/Models/CardTaskFetchValue.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleIds.cs b/src/samples/test-bots/Teams/TaskModule/Models/TaskModuleIds.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleIds.cs rename to src/samples/test-bots/Teams/TaskModule/Models/TaskModuleIds.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleResponseFactory.cs b/src/samples/test-bots/Teams/TaskModule/Models/TaskModuleResponseFactory.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleResponseFactory.cs rename to src/samples/test-bots/Teams/TaskModule/Models/TaskModuleResponseFactory.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleUIConstants.cs b/src/samples/test-bots/Teams/TaskModule/Models/TaskModuleUIConstants.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/TaskModuleUIConstants.cs rename to src/samples/test-bots/Teams/TaskModule/Models/TaskModuleUIConstants.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Models/UISettings.cs b/src/samples/test-bots/Teams/TaskModule/Models/UISettings.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Models/UISettings.cs rename to src/samples/test-bots/Teams/TaskModule/Models/UISettings.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/Configure.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/Configure.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/Configure.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/Configure.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/CustomForm.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/CustomForm.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml.cs b/src/samples/test-bots/Teams/TaskModule/Pages/CustomForm.cshtml.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/CustomForm.cshtml.cs rename to src/samples/test-bots/Teams/TaskModule/Pages/CustomForm.cshtml.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/HelloWorld.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/HelloWorld.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/HelloWorld.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/HelloWorld.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/PowerApp.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/PowerApp.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/PowerApp.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/PowerApp.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/Shared/_EmbedPage.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_Layout.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/Shared/_Layout.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/Shared/_Layout.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/Shared/_Layout.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/Tasks.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/Tasks.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/Tasks.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/Tasks.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/YouTube.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/YouTube.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/YouTube.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/YouTube.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Pages/_ViewStart.cshtml b/src/samples/test-bots/Teams/TaskModule/Pages/_ViewStart.cshtml similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Pages/_ViewStart.cshtml rename to src/samples/test-bots/Teams/TaskModule/Pages/_ViewStart.cshtml diff --git a/src/samples/ToMigrate/Teams/TaskModule/Program.cs b/src/samples/test-bots/Teams/TaskModule/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Program.cs rename to src/samples/test-bots/Teams/TaskModule/Program.cs diff --git a/src/samples/ToMigrate/Teams/TaskModule/README.md b/src/samples/test-bots/Teams/TaskModule/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/README.md rename to src/samples/test-bots/Teams/TaskModule/README.md diff --git a/src/samples/ToMigrate/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json b/src/samples/test-bots/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json rename to src/samples/test-bots/Teams/TaskModule/Resources/AdaptiveCard_TaskModule.json diff --git a/src/samples/ToMigrate/Teams/TaskModule/Resources/adaptiveCard.json b/src/samples/test-bots/Teams/TaskModule/Resources/adaptiveCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/Resources/adaptiveCard.json rename to src/samples/test-bots/Teams/TaskModule/Resources/adaptiveCard.json diff --git a/src/samples/ToMigrate/Teams/TaskModule/TaskModule.csproj b/src/samples/test-bots/Teams/TaskModule/TaskModule.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/TaskModule.csproj rename to src/samples/test-bots/Teams/TaskModule/TaskModule.csproj diff --git a/src/samples/ToMigrate/Teams/TaskModule/appManifest/color.png b/src/samples/test-bots/Teams/TaskModule/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/appManifest/color.png rename to src/samples/test-bots/Teams/TaskModule/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/appManifest/manifest.json b/src/samples/test-bots/Teams/TaskModule/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/appManifest/manifest.json rename to src/samples/test-bots/Teams/TaskModule/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/TaskModule/appManifest/outline.png b/src/samples/test-bots/Teams/TaskModule/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/appManifest/outline.png rename to src/samples/test-bots/Teams/TaskModule/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/TaskModule/appsettings.json b/src/samples/test-bots/Teams/TaskModule/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/appsettings.json rename to src/samples/test-bots/Teams/TaskModule/appsettings.json diff --git a/src/samples/ToMigrate/Teams/TaskModule/assets/sample.json b/src/samples/test-bots/Teams/TaskModule/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/assets/sample.json rename to src/samples/test-bots/Teams/TaskModule/assets/sample.json diff --git a/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/Site.css b/src/samples/test-bots/Teams/TaskModule/wwwroot/css/Site.css similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/Site.css rename to src/samples/test-bots/Teams/TaskModule/wwwroot/css/Site.css diff --git a/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/custom.css b/src/samples/test-bots/Teams/TaskModule/wwwroot/css/custom.css similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/custom.css rename to src/samples/test-bots/Teams/TaskModule/wwwroot/css/custom.css diff --git a/src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/msteams-16.css b/src/samples/test-bots/Teams/TaskModule/wwwroot/css/msteams-16.css similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/wwwroot/css/msteams-16.css rename to src/samples/test-bots/Teams/TaskModule/wwwroot/css/msteams-16.css diff --git a/src/samples/ToMigrate/Teams/TaskModule/wwwroot/default.htm b/src/samples/test-bots/Teams/TaskModule/wwwroot/default.htm similarity index 100% rename from src/samples/ToMigrate/Teams/TaskModule/wwwroot/default.htm rename to src/samples/test-bots/Teams/TaskModule/wwwroot/default.htm diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/BotAllCards.csproj b/src/samples/test-bots/Teams/bot-all-cards/BotAllCards.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/BotAllCards.csproj rename to src/samples/test-bots/Teams/bot-all-cards/BotAllCards.csproj diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Bots/DialogBot.cs b/src/samples/test-bots/Teams/bot-all-cards/Bots/DialogBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Bots/DialogBot.cs rename to src/samples/test-bots/Teams/bot-all-cards/Bots/DialogBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Bots/TeamsBot.cs b/src/samples/test-bots/Teams/bot-all-cards/Bots/TeamsBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Bots/TeamsBot.cs rename to src/samples/test-bots/Teams/bot-all-cards/Bots/TeamsBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Cards/AllCards.cs b/src/samples/test-bots/Teams/bot-all-cards/Cards/AllCards.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Cards/AllCards.cs rename to src/samples/test-bots/Teams/bot-all-cards/Cards/AllCards.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-all-cards/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-all-cards/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Dialogs/MainDialog.cs b/src/samples/test-bots/Teams/bot-all-cards/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Dialogs/MainDialog.cs rename to src/samples/test-bots/Teams/bot-all-cards/Dialogs/MainDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/1.Install.png b/src/samples/test-bots/Teams/bot-all-cards/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/1.Install.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/10.CollectionCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/10.CollectionCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/10.CollectionCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/10.CollectionCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/11.ConnectorCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/11.ConnectorCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/11.ConnectorCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/11.ConnectorCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/2.Welcome.png b/src/samples/test-bots/Teams/bot-all-cards/Images/2.Welcome.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/2.Welcome.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/2.Welcome.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/3.SelectCards.png b/src/samples/test-bots/Teams/bot-all-cards/Images/3.SelectCards.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/3.SelectCards.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/3.SelectCards.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/4.AdaptiveCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/4.AdaptiveCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/4.AdaptiveCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/4.AdaptiveCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/5.HeroCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/5.HeroCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/5.HeroCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/5.HeroCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/6.OathCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/6.OathCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/6.OathCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/6.OathCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/7.SignInCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/7.SignInCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/7.SignInCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/7.SignInCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/8.ThumbnailCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/8.ThumbnailCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/8.ThumbnailCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/8.ThumbnailCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/9.ListCard.png b/src/samples/test-bots/Teams/bot-all-cards/Images/9.ListCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/9.ListCard.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/9.ListCard.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia.png b/src/samples/test-bots/Teams/bot-all-cards/Images/AdaptiveCardMedia.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/AdaptiveCardMedia.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png b/src/samples/test-bots/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/AdaptiveCardMedia2.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/Authentication.png b/src/samples/test-bots/Teams/bot-all-cards/Images/Authentication.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/Authentication.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/Authentication.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/OauthConnection.png b/src/samples/test-bots/Teams/bot-all-cards/Images/OauthConnection.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/OauthConnection.png rename to src/samples/test-bots/Teams/bot-all-cards/Images/OauthConnection.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Images/allBotCardsGif.gif b/src/samples/test-bots/Teams/bot-all-cards/Images/allBotCardsGif.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Images/allBotCardsGif.gif rename to src/samples/test-bots/Teams/bot-all-cards/Images/allBotCardsGif.gif diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Program.cs b/src/samples/test-bots/Teams/bot-all-cards/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Program.cs rename to src/samples/test-bots/Teams/bot-all-cards/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/README.md b/src/samples/test-bots/Teams/bot-all-cards/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/README.md rename to src/samples/test-bots/Teams/bot-all-cards/README.md diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/adaptiveCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/adaptiveCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCardMedia.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/adaptiveCardMedia.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/adaptiveCardMedia.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/adaptiveCardMedia.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/collectionsCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/collectionsCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/collectionsCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/collectionsCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/heroCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/heroCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/heroCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/heroCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/listCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/listCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/listCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/listCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/o365ConnectorCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/o365ConnectorCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/o365ConnectorCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/o365ConnectorCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/Resources/thumbnailCard.json b/src/samples/test-bots/Teams/bot-all-cards/Resources/thumbnailCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/Resources/thumbnailCard.json rename to src/samples/test-bots/Teams/bot-all-cards/Resources/thumbnailCard.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/color.png b/src/samples/test-bots/Teams/bot-all-cards/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/appManifest/color.png rename to src/samples/test-bots/Teams/bot-all-cards/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/manifest.json b/src/samples/test-bots/Teams/bot-all-cards/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/appManifest/manifest.json rename to src/samples/test-bots/Teams/bot-all-cards/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/appManifest/outline.png b/src/samples/test-bots/Teams/bot-all-cards/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/appManifest/outline.png rename to src/samples/test-bots/Teams/bot-all-cards/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/appsettings.json b/src/samples/test-bots/Teams/bot-all-cards/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/appsettings.json rename to src/samples/test-bots/Teams/bot-all-cards/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-all-cards/wwwroot/default.htm b/src/samples/test-bots/Teams/bot-all-cards/wwwroot/default.htm similarity index 100% rename from src/samples/ToMigrate/Teams/bot-all-cards/wwwroot/default.htm rename to src/samples/test-bots/Teams/bot-all-cards/wwwroot/default.htm diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj rename to src/samples/test-bots/Teams/bot-conversation-sso-quickstart/BotConversationSsoQuickstart.csproj diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs new file mode 100644 index 00000000..46c59d48 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/DialogBot.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Agents.Extensions.Teams.Compat; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.BotBuilder; + +namespace BotConversationSsoQuickstart.Bots +{ + // This IBot implementation can run any type of Dialog. The use of type parameterization is to allows multiple different bots + // to be run at different endpoints within the same project. This can be achieved by defining distinct Controller types + // each with dependency on distinct IBot types, this way ASP Dependency Injection can glue everything together without ambiguity. + // The ConversationState is used by the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, + // and the requirement is that all BotState objects are saved at the end of a turn. + public class DialogBot(ConversationState conversationState, T dialog, ILogger> logger) : TeamsActivityHandler where T : Dialog + { + protected readonly BotState _conversationState = conversationState; + protected readonly Dialog _dialog = dialog; + protected readonly ILogger _logger = logger; + + ///

+ /// Handle when a message is addressed to the bot. + /// + /// Context object containing information cached for a single turn of conversation with a user. + /// Propagates notification that operations should be canceled. + /// A Task resolving to either a login card or the adaptive card of the Reddit post. + /// + /// For more information on bot messaging in Teams, see the documentation + /// https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/conversation-basics?tabs=dotnet#receive-a-message . + /// + protected override async Task OnMessageActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + _logger.LogInformation("Running dialog with Message Activity."); + await _dialog.RunAsync(turnContext, _conversationState, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs new file mode 100644 index 00000000..b16e2fb9 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Bots/TeamsBot.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Logging; + +namespace BotConversationSsoQuickstart.Bots +{ + // This bot is derived (view DialogBot) from the TeamsActivityHandler class currently included as part of this sample. + public class TeamsBot(ConversationState conversationState, T dialog, ILogger> logger) + : DialogBot(conversationState, dialog, logger) where T : Dialog + { + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken); + } + } + } + + protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + _logger.LogInformation("Running dialog with signin/verifystate from an Invoke Activity."); + + // The OAuth Prompt needs to see the Invoke Activity in order to complete the login process. + // Run the Dialog with the new Invoke Activity. + await _dialog.RunAsync(turnContext, _conversationState, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs new file mode 100644 index 00000000..38ff3031 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Controllers/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.BotBuilder; + +namespace BotConversationSsoQuickstart.Controllers +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs new file mode 100644 index 00000000..51e2ac07 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/LogoutDialog.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.Connector; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.Core.Models; + +namespace BotConversationSsoQuickstart.Dialogs +{ + public class LogoutDialog : ComponentDialog + { + public LogoutDialog(string id, string connectionName) + : base(id) + { + ConnectionName = connectionName; + } + + /// + /// Configured connection name in Azure Bot service. + /// + protected string ConnectionName { get; } + + /// + /// Called when the dialog is started and pushed onto the parent's dialog stack. + /// + /// The inner DialogContext for the current turn of conversation. + /// Initial information to pass to the dialog. + /// Propagates notification that operations should be canceled. + /// A task representing the asynchronous operation. + protected override async Task OnBeginDialogAsync( + DialogContext innerDc, + object options, + CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnBeginDialogAsync(innerDc, options, cancellationToken); + } + + /// + /// Called when the dialog is _continued_, where it is the active dialog and the user replies with a new activity. + /// + /// The inner DialogContext for the current turn of conversation. + /// Propagates notification that operations should be canceled. + /// A task representing the asynchronous operation. + protected override async Task OnContinueDialogAsync( + DialogContext innerDc, + CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await InterruptAsync(innerDc, cancellationToken); + if (result != null) + { + return result; + } + + return await base.OnContinueDialogAsync(innerDc, cancellationToken); + } + + /// + /// Called when the dialog is interrupted, where it is the active dialog and the user replies with a new activity. + /// + /// The inner DialogContext for the current turn of conversation. + /// Propagates notification that operations should be canceled. + /// + private async Task InterruptAsync( + DialogContext innerDc, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (innerDc.Context.Activity.Type == ActivityTypes.Message) + { + var text = innerDc.Context.Activity.Text?.ToLowerInvariant(); + + // Allow logout anywhere in the command + if (text != null && text.IndexOf("logout") >= 0) + { + // The UserTokenClient encapsulates the authentication processes. + var userTokenClient = innerDc.Context.Services.Get(); + await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken).ConfigureAwait(false); + + await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken); + return await innerDc.CancelAllDialogsAsync(cancellationToken); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs new file mode 100644 index 00000000..1aea96bc --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Dialogs/MainDialog.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder.Dialogs; +using Microsoft.Agents.Core.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace BotConversationSsoQuickstart.Dialogs +{ + public class MainDialog : LogoutDialog + { + protected readonly ILogger _logger; + + public MainDialog(IConfiguration configuration, ILogger logger) + : base(nameof(MainDialog), configuration["ConnectionName"]) + { + _logger = logger; + + AddDialog(new OAuthPrompt( + nameof(OAuthPrompt), + new OAuthPromptSettings + { + ConnectionName = ConnectionName, + Text = "Please Sign In", + Title = "Sign In", + Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) + EndOnInvalidMessage = true + })); + + AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); + + AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] + { + PromptStepAsync, + LoginStepAsync, + DisplayTokenPhase1Async, + DisplayTokenPhase2Async, + })); + + // The initial child Dialog to run. + InitialDialogId = nameof(WaterfallDialog); + } + + private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + _logger.LogInformation("PromptStepAsync() called."); + return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); + } + + private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Get the token from the previous step. Note that we could also have gotten the + // token directly from the prompt itself. There is an example of this in the next method. + var tokenResponse = (TokenResponse)stepContext.Result; + if (tokenResponse?.Token != null) + { + try + { + // Pull in the data from the Microsoft Graph. + var client = new SimpleGraphClient(tokenResponse.Token); + var me = await client.GetMeAsync(); + var title = !string.IsNullOrEmpty(me.JobTitle) ? + me.JobTitle : "Unknown"; + + await stepContext.Context.SendActivityAsync($"You're logged in as {me.DisplayName} ({me.UserPrincipalName}); you job title is: {title}"); + + var photo = await client.GetPhotoAsync(); + + if (photo !="") { + var cardImage = new CardImage(photo); + var card = new ThumbnailCard(images: new List() { cardImage }); + var reply = MessageFactory.Attachment(card.ToAttachment()); + + await stepContext.Context.SendActivityAsync(reply, cancellationToken); + } + else + { + var profile_Message = "Sorry !! User doesn't have a profile picture to display"; + await stepContext.Context.SendActivityAsync(profile_Message); + } + + return await stepContext.PromptAsync( + nameof(ConfirmPrompt), + new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your token?") }, + cancellationToken); + } + catch (Exception ex) + { + _logger.LogError($"Error occurred while processing your request. {ex.Message}"); + } + } + else + { + _logger.LogInformation("Response token is null or empty."); + } + + await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken); + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + + private async Task DisplayTokenPhase1Async( + WaterfallStepContext stepContext, + CancellationToken cancellationToken) + { + _logger.LogInformation("DisplayTokenPhase1Async() method called."); + + await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken); + + var result = (bool)stepContext.Result; + if (result) + { + // Call the prompt again because we need the token. The reasons for this are: + // 1. If the user is already logged in we do not need to store the token locally in the bot and worry + // about refreshing it. We can always just call the prompt again to get the token. + // 2. We never know how long it will take a user to respond. By the time the + // user responds the token may have expired. The user would then be prompted to login again. + // + // There is no reason to store the token locally in the bot because we can always just call + // the OAuth prompt to get the token or get a new token if needed. + return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), cancellationToken: cancellationToken); + } + + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + + private async Task DisplayTokenPhase2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + _logger.LogInformation("DisplayTokenPhase2Async() method called."); + + var tokenResponse = (TokenResponse)stepContext.Result; + if (tokenResponse != null) + { + await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Here is your token {tokenResponse.Token}"), cancellationToken); + } + + return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/1.Install.png b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/1.Install.png new file mode 100644 index 0000000000000000000000000000000000000000..3097dd592d23724872c957b5d8bef012374c844e GIT binary patch literal 82591 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¨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
    70RG|LbPYdRRqF3^|a9bwf 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 #L^fk1~yq+2sj_Co@Ex zvUK~RDFqdQ83h|E@Cu%75~25^!BG>c33Oo=oB1Fszt^~H>$;`7V)ug^~nd$Xp+84`6vW}fNgGVHapbTB`d$VN0+uC=fk zYt^L1Gu5%N&MxWwaGmYc$+{gp;aU`yY+yr*fLmRgvd4>XDUFDI;H_{RRCBa-y<_Uq zA)zNGDT0P3{q(DruJtcejh^Sxcuox$r>-1g+y6m)0eUGCd90r){<8KP=-a_V8KZ1s zk~OGWb!jq=&(}ha^6O5|z3^TCV0aE1H+;np?v^uO)*$@VUK>=IQzB_448e#_XcWUX zGq(WrAIXV{Nvik+siwZCmiUhzm+PCGHyw{k01K4wL}Ykg=}ZFXlo{C<^nuCgB5OA1S}G`?^6%oV#kl#xfColt(D$-IzO>P0L~+?2{5vk zfC{NPch~Rgv`E9`MLQPx=U%q#t?1A>(CIq(h#x&s<$7G-STwh4w?UX{^k}B!bgmTy22W~(HLccnK@n;{^+O-v*yRmv#%*dW40Ag{WZal-qVb0jGJ;9unysNk>Dx?E^^cc9dfye^6xc5p_+E{F z-2vch{i~TQ$bE~e0U)ZDGg2E0Z6?d%BIwesbvjt#fyxZnP z@5*5McQVxbQ#I?C5pz9jmpA>Vz$~Ahm4$^x<@)NvBXO21(!#~#m?BTBo2FSnL0tx6)|86Jpf0rHLl_XL5p9A@SY*#+UYm92A V;`fS!Br&LSQc-vyUny%A_#gBqP`Cg9 literal 0 HcmV?d00001 diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/Images/BotConversationSsoQuickStart.gif new file mode 100644 index 0000000000000000000000000000000000000000..e13571b90ee541e7b70db3a7e397e4930f4018af GIT binary patch literal 212649 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 SOfmtVeDNH>oE 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 d The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) + + 1. Open the `appsettings.json` file in the root of the sample project. + + 1. Find the section labeled `Connections`, it should appear similar to this: + + ```json + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Assembly": "Microsoft.Agents.Authentication.Msal", + "Type": "MsalAuth", + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ], + "TenantId": "{{TenantId}}" // This is the Tenant ID used for the Connection. + } + } + ``` + + 1. Set the **ClientId** to the AppId of the bot identity. + 1. Set the **ClientSecret** to the Secret that was created for your identity. + 1. Set the **TenantId** to the Tenant Id where your application is registered. + 1. Set the **Audience** to the AppId of the bot identity. + + > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. + +1. Set "ConnectionName" in the `appsettings.json`. The Microsoft Entra ID ConnectionName from the OAuth Connection Settings on Azure Bot registration + +1. Manually update the manifest.json + - Edit the `manifest.json` contained in the `/appManifest` folder + - Replace with your AppId (that was created above) *everywhere* you see the place holder string `<>` + - Replace `<>` with your Agent url. For example, the tunnel host name. + - Zip up the contents of the `/appManifest` folder to create a `manifest.zip` +1. Upload the `manifest.zip` to Teams + - Select **Developer Portal** in the Teams left sidebar + - Select **Apps** (top row) + - Select **Import app**, and select the manifest.zip + +1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and use that port number in the devtunnel command (instead of 3978). + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + +1. On the Azure Bot, select **Settings**, then **Configuration**, and update the **Messaging endpoint** to `{tunnel-url}/api/messages` + +1. Start the Agent, and select **Preview in Teams** in the upper right corner + +## Interacting with this Agent in Teams + +![Install](Images/1.Install.png) + +![bot signin card](Images/2.Installed.png) + +![user details card](Images/3.Logged_In.png) + +![token](Images/4.Your_Token.png) + +## Further reading +To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs new file mode 100644 index 00000000..2b312bf8 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/SimpleGraphClient.cs @@ -0,0 +1,131 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.Graph; + +namespace BotConversationSsoQuickstart +{ + // This class is a wrapper for the Microsoft Graph API + // See: https://developer.microsoft.com/en-us/graph + public class SimpleGraphClient + { + private readonly string _token; + + public SimpleGraphClient(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); + } + + _token = token; + } + + // Sends an email on the users behalf using the Microsoft Graph API + public async Task SendMailAsync(string toAddress, string subject, string content) + { + if (string.IsNullOrWhiteSpace(toAddress)) + { + throw new ArgumentNullException(nameof(toAddress)); + } + + if (string.IsNullOrWhiteSpace(subject)) + { + throw new ArgumentNullException(nameof(subject)); + } + + if (string.IsNullOrWhiteSpace(content)) + { + throw new ArgumentNullException(nameof(content)); + } + + var graphClient = GetAuthenticatedClient(); + var recipients = new List + { + new Recipient + { + EmailAddress = new EmailAddress + { + Address = toAddress, + }, + }, + }; + + // Create the message. + var email = new Message + { + Body = new ItemBody + { + Content = content, + ContentType = BodyType.Text, + }, + Subject = subject, + ToRecipients = recipients, + }; + + // Send the message. + await graphClient.Me.SendMail(email, true).Request().PostAsync(); + } + + // Gets mail for the user using the Microsoft Graph API + public async Task GetRecentMailAsync() + { + var graphClient = GetAuthenticatedClient(); + var messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync(); + return messages.Take(5).ToArray(); + } + + // Get information about the user. + public async Task GetMeAsync() + { + var graphClient = GetAuthenticatedClient(); + var me = await graphClient.Me.Request().GetAsync(); + return me; + } + + // Gets the user's photo + public async Task GetPhotoAsync() + { + var graphClient = GetAuthenticatedClient(); + var photo = await graphClient.Me.Photo.Content.Request().GetAsync(); + if (photo != null) + { + MemoryStream ms = new MemoryStream(); + photo.CopyTo(ms); + byte[] buffers = ms.ToArray(); + string imgDataURL = string.Format("data:image/png;base64,{0}", Convert.ToBase64String(buffers)); + return imgDataURL; + } + else + { + return ""; + } + } + + // Get an Authenticated Microsoft Graph client using the token issued to the user. + private GraphServiceClient GetAuthenticatedClient() + { + var graphClient = new GraphServiceClient( + new DelegateAuthenticationProvider( + requestMessage => + { + // Append the access token to the request. + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _token); + + // Get event times in the current time zone. + requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\""); + + return Task.CompletedTask; + })); + + return graphClient; + } + } +} diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs new file mode 100644 index 00000000..a50d8ba5 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/TeamsSSOAdapter.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +using System; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue; +using Microsoft.Extensions.Logging; +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.State; + +namespace BotConversationSsoQuickstart +{ + public class TeamsSSOAdapter : CloudAdapter + { + public TeamsSSOAdapter( + IChannelServiceClientFactory channelServiceClientFactory, + IActivityTaskQueue activityTaskQueue, + ILogger logger, + ConversationState conversationState, + IMiddleware[] middlewares = null) + : base(channelServiceClientFactory, activityTaskQueue, logger: logger, middlewares: middlewares) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Uncomment below commented line for local debugging.. + // await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteStateAsync(turnContext); + } + catch (Exception e) + { + logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync( + "OnTurnError Trace", + exception.Message, + "https://www.botframework.com/schemas/error", + "TurnError"); + }; + } + } +} \ No newline at end of file diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/color.png b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/color.png rename to src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/color.png diff --git a/src/samples/Compat/AuthenticationBotCompat/appManifest/manifest.json b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json similarity index 62% rename from src/samples/Compat/AuthenticationBotCompat/appManifest/manifest.json rename to src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json index 66882f98..7a714c07 100644 --- a/src/samples/Compat/AuthenticationBotCompat/appManifest/manifest.json +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/manifest.json @@ -2,10 +2,10 @@ "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", "manifestVersion": "1.16", "version": "1.0.0", - "id": "${{AAD_APP_CLIENT_ID}}", - "packageName": "com.microsoft.agents.oauth", + "id": "<>", + "packageName": "com.Microsoft.Agents.Extensions.Teams.conversationsso", "developer": { - "name": "Microsoft, Inc.", + "name": "Teams App, Inc.", "websiteUrl": "https://example.azurewebsites.net", "privacyUrl": "https://example.azurewebsites.net/privacy", "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" @@ -15,17 +15,17 @@ "outline": "outline.png" }, "name": { - "short": "OAuth Authentication", - "full": "OAuth Authentication" + "short": "Conversation Bot", + "full": "Conversation Bot" }, "description": { - "short": "Sample demonstrating Azure Bot Services user authentication with using a Agent.", - "full": "This sample demonstrates how to integrate Azure AD authentication in an Agent with Single Sign-On (SSO) capabilities built with the Agents Framework" + "short": "Sample demonstrating Azure AD authentication with Teams SSO in a conversation.", + "full": "This sample demonstrates how to integrate Azure AD authentication in Microsoft Teams using a bot with Single Sign-On (SSO) capabilities built with the Co-Pilot SDK" }, "accentColor": "#FFFFFF", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "<>", "scopes": [ "personal" ], @@ -42,7 +42,7 @@ "<>" ], "webApplicationInfo": { - "id": "${{AAD_APP_CLIENT_ID}}", - "resource": "api://botid-${{AAD_APP_CLIENT_ID}}" + "id": "<>", + "resource": "api://botid-<>" } } \ No newline at end of file diff --git a/src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/outline.png b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-conversation-sso-quickstart/appManifest/outline.png rename to src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appManifest/outline.png diff --git a/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appsettings.json b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appsettings.json new file mode 100644 index 00000000..a86e8b83 --- /dev/null +++ b/src/samples/test-bots/Teams/bot-conversation-sso-quickstart/appsettings.json @@ -0,0 +1,38 @@ +{ + "ConnectionName": "{{Connection-Name}}", + + "TokenValidation": { + "Audiences": [ + "00000000-0000-0000-0000-000000000000" // this is the Client ID used for the Azure Bot + ] + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "00000000-0000-0000-0000-000000000000", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Bots/ActivityBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Cards/PersonalScopeCard.json diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Cards/TeamsScopeCard.json diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/PepolePickerAdaptiveCard.gif diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/Welcome.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/Welcome.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/Welcome.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/Welcome.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-card.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-id.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Images/people-picker-info.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/PeoplePicker.csproj diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Program.cs b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/Program.cs rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/README.md b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/README.md rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/README.md diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/color.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/color.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/outline.png b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appManifest/outline.png rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appsettings.json b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/appsettings.json rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/assets/sample.json b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/assets/sample.json rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/assets/sample.json diff --git a/src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm b/src/samples/test-bots/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm similarity index 100% rename from src/samples/ToMigrate/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm rename to src/samples/test-bots/Teams/bot-people-picker-adaptive-card/wwwroot/default.htm diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Bots/ActivityBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/2.Installed.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/3.Interaction.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/4.1_and_2_Command_Interaction.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/5.Install_to_GC.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/6.Installed.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/7.1_and_2_Command_Interaction.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Images/Bot_Channel_Messenging-RSC-nodejs-gif.gif diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Program.cs b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/Program.cs rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/README.md b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/README.md rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/README.md diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/ReceiveMessagesWithRSC.csproj diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/color.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appsettings.json b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/appsettings.json rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/assets/sample.json b/src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-receive-channel-messages-withRSC/assets/sample.json rename to src/samples/test-bots/Teams/bot-receive-channel-messages-withRSC/assets/sample.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/BotRequestApproval.csproj b/src/samples/test-bots/Teams/bot-request-approval/BotRequestApproval.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/BotRequestApproval.csproj rename to src/samples/test-bots/Teams/bot-request-approval/BotRequestApproval.csproj diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Bots/ActivityBot.cs b/src/samples/test-bots/Teams/bot-request-approval/Bots/ActivityBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Bots/ActivityBot.cs rename to src/samples/test-bots/Teams/bot-request-approval/Bots/ActivityBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/ApprovedCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/ApprovedCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/ApprovedCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/ApprovedCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/AssignedToCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/AssignedToCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/AssignedToCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/AssignedToCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/CancelCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/CancelCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/CancelCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/CancelCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/InitialCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/InitialCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/InitialCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/InitialCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/OtherMembersCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/OtherMembersCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/OtherMembersCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/OtherMembersCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RejectedCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/RejectedCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/RejectedCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/RejectedCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestCard.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/RequestCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestCard.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/RequestCard.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json b/src/samples/test-bots/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json rename to src/samples/test-bots/Teams/bot-request-approval/Cards/RequestDetailsCardForUser.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-request-approval/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-request-approval/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/ApproveRejectCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/ApproveRejectCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/ApproveRejectCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/ApproveRejectCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/ApprovedRequest.png b/src/samples/test-bots/Teams/bot-request-approval/Images/ApprovedRequest.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/ApprovedRequest.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/ApprovedRequest.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/CancelledRequest.png b/src/samples/test-bots/Teams/bot-request-approval/Images/CancelledRequest.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/CancelledRequest.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/CancelledRequest.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/CreateTask.png b/src/samples/test-bots/Teams/bot-request-approval/Images/CreateTask.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/CreateTask.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/CreateTask.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/EditCancelCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/EditCancelCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/EditCancelCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/EditCancelCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/EditTask.png b/src/samples/test-bots/Teams/bot-request-approval/Images/EditTask.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/EditTask.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/EditTask.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/InitialCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/InitialCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/InitialCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/InitialCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/ManagerCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/ManagerCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/ManagerCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/ManagerCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/OtherMemberCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/OtherMemberCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/OtherMemberCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/OtherMemberCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/Preview.gif b/src/samples/test-bots/Teams/bot-request-approval/Images/Preview.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/Preview.gif rename to src/samples/test-bots/Teams/bot-request-approval/Images/Preview.gif diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/RequestCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/RequestCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/RequestCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/RequestCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/StatusCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/StatusCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/StatusCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/StatusCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Images/UserCard.png b/src/samples/test-bots/Teams/bot-request-approval/Images/UserCard.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Images/UserCard.png rename to src/samples/test-bots/Teams/bot-request-approval/Images/UserCard.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Models/InitialSequentialCard.cs b/src/samples/test-bots/Teams/bot-request-approval/Models/InitialSequentialCard.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Models/InitialSequentialCard.cs rename to src/samples/test-bots/Teams/bot-request-approval/Models/InitialSequentialCard.cs diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Models/RequestDetails.cs b/src/samples/test-bots/Teams/bot-request-approval/Models/RequestDetails.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Models/RequestDetails.cs rename to src/samples/test-bots/Teams/bot-request-approval/Models/RequestDetails.cs diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/Program.cs b/src/samples/test-bots/Teams/bot-request-approval/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/Program.cs rename to src/samples/test-bots/Teams/bot-request-approval/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/README.md b/src/samples/test-bots/Teams/bot-request-approval/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/README.md rename to src/samples/test-bots/Teams/bot-request-approval/README.md diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/color.png b/src/samples/test-bots/Teams/bot-request-approval/appPackage/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/appPackage/color.png rename to src/samples/test-bots/Teams/bot-request-approval/appPackage/color.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/manifest.json b/src/samples/test-bots/Teams/bot-request-approval/appPackage/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/appPackage/manifest.json rename to src/samples/test-bots/Teams/bot-request-approval/appPackage/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/appPackage/outline.png b/src/samples/test-bots/Teams/bot-request-approval/appPackage/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/appPackage/outline.png rename to src/samples/test-bots/Teams/bot-request-approval/appPackage/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/appsettings.json b/src/samples/test-bots/Teams/bot-request-approval/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/appsettings.json rename to src/samples/test-bots/Teams/bot-request-approval/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-request-approval/assets/sample.json b/src/samples/test-bots/Teams/bot-request-approval/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-request-approval/assets/sample.json rename to src/samples/test-bots/Teams/bot-request-approval/assets/sample.json diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Bots/DialogBot.cs b/src/samples/test-bots/Teams/bot-tag-mention/Bots/DialogBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Bots/DialogBot.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Bots/DialogBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs b/src/samples/test-bots/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Bots/TeamsTagMentionBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-tag-mention/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs b/src/samples/test-bots/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Dialogs/LogoutDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/MainDialog.cs b/src/samples/test-bots/Teams/bot-tag-mention/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Dialogs/MainDialog.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Dialogs/MainDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/1.AddPersonalScope.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/1.AddPersonalScope.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/1.AddPersonalScope.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/1.AddPersonalScope.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/2.LoginWithPersonalScope.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/3.AddToTeamsScope.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/4.WelcomeMessage_Teams.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag-2.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/5.MetionedTag-2.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag-2.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/5.MetionedTag-2.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/5.MetionedTag.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/5.MetionedTag.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/5.MetionedTag.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/6.TagMentionDetails.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/6.TagMentionDetails.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/6.TagMentionDetails.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/6.TagMentionDetails.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/7.MessageWhenNoTagFound.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/8.WithOutCommand.png b/src/samples/test-bots/Teams/bot-tag-mention/Images/8.WithOutCommand.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/8.WithOutCommand.png rename to src/samples/test-bots/Teams/bot-tag-mention/Images/8.WithOutCommand.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Images/Tag-mention-bot.gif b/src/samples/test-bots/Teams/bot-tag-mention/Images/Tag-mention-bot.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Images/Tag-mention-bot.gif rename to src/samples/test-bots/Teams/bot-tag-mention/Images/Tag-mention-bot.gif diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Program.cs b/src/samples/test-bots/Teams/bot-tag-mention/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Program.cs rename to src/samples/test-bots/Teams/bot-tag-mention/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/README.md b/src/samples/test-bots/Teams/bot-tag-mention/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/README.md rename to src/samples/test-bots/Teams/bot-tag-mention/README.md diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json b/src/samples/test-bots/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json rename to src/samples/test-bots/Teams/bot-tag-mention/Resources/UserMentionCardTemplate.json diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/SimpleGraphClient.cs b/src/samples/test-bots/Teams/bot-tag-mention/SimpleGraphClient.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/SimpleGraphClient.cs rename to src/samples/test-bots/Teams/bot-tag-mention/SimpleGraphClient.cs diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/TagMentionBot.csproj b/src/samples/test-bots/Teams/bot-tag-mention/TagMentionBot.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/TagMentionBot.csproj rename to src/samples/test-bots/Teams/bot-tag-mention/TagMentionBot.csproj diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-color.png b/src/samples/test-bots/Teams/bot-tag-mention/appManifest/icon-color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-color.png rename to src/samples/test-bots/Teams/bot-tag-mention/appManifest/icon-color.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-outline.png b/src/samples/test-bots/Teams/bot-tag-mention/appManifest/icon-outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/icon-outline.png rename to src/samples/test-bots/Teams/bot-tag-mention/appManifest/icon-outline.png diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/manifest.json b/src/samples/test-bots/Teams/bot-tag-mention/appManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/appManifest/manifest.json rename to src/samples/test-bots/Teams/bot-tag-mention/appManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-tag-mention/appsettings.json b/src/samples/test-bots/Teams/bot-tag-mention/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-tag-mention/appsettings.json rename to src/samples/test-bots/Teams/bot-tag-mention/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/color.png b/src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/color.png rename to src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/color.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/manifest.json b/src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/manifest.json rename to src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/outline.png b/src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/AppManifest/outline.png rename to src/samples/test-bots/Teams/bot-teams-authentication/AppManifest/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/DialogBot.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Bots/DialogBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/DialogBot.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Bots/DialogBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/TeamsBot.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Bots/TeamsBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Bots/TeamsBot.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Bots/TeamsBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Dialogs/LogoutDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/MainDialog.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Dialogs/MainDialog.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Dialogs/MainDialog.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Dialogs/MainDialog.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/1.Install.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/1.Install.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/2.Welcome.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/2.Welcome.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/2.Welcome.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/2.Welcome.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/3.AuthSuccess.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/3.AuthSuccess.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/3.AuthSuccess.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/3.AuthSuccess.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/4.AuthToken.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/4.AuthToken.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/4.AuthToken.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/4.AuthToken.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/5.Logout.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/5.Logout.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/5.Logout.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/5.Logout.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/auth-consent.png b/src/samples/test-bots/Teams/bot-teams-authentication/Images/auth-consent.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/auth-consent.png rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/auth-consent.png diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Images/bot-teams-auth.gif b/src/samples/test-bots/Teams/bot-teams-authentication/Images/bot-teams-auth.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Images/bot-teams-auth.gif rename to src/samples/test-bots/Teams/bot-teams-authentication/Images/bot-teams-auth.gif diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/Program.cs b/src/samples/test-bots/Teams/bot-teams-authentication/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/Program.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/README.md b/src/samples/test-bots/Teams/bot-teams-authentication/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/README.md rename to src/samples/test-bots/Teams/bot-teams-authentication/README.md diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/SimpleGraphClient.cs b/src/samples/test-bots/Teams/bot-teams-authentication/SimpleGraphClient.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/SimpleGraphClient.cs rename to src/samples/test-bots/Teams/bot-teams-authentication/SimpleGraphClient.cs diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/TeamsAuth.csproj b/src/samples/test-bots/Teams/bot-teams-authentication/TeamsAuth.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/TeamsAuth.csproj rename to src/samples/test-bots/Teams/bot-teams-authentication/TeamsAuth.csproj diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/appsettings.json b/src/samples/test-bots/Teams/bot-teams-authentication/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/appsettings.json rename to src/samples/test-bots/Teams/bot-teams-authentication/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-teams-authentication/assets/sample.json b/src/samples/test-bots/Teams/bot-teams-authentication/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-teams-authentication/assets/sample.json rename to src/samples/test-bots/Teams/bot-teams-authentication/assets/sample.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Bots/ActivityBot.cs diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/DependentDropdown.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/DynamicSearchCard.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Cards/StaticSearchCard.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Controllers/BotController.cs diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/1.Install.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/10.CountryOptions.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/11.CitiesAsPerTheCountry.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/12.SelectedDependantDropdown.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/2.Welcome.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/3.StaticSearch.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/4.StaticSearch2.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/5.SelectedOption.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/6.DynamicSearch.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/7.DynamicSearch2.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/8.SelectedDynamicSearch.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/9.DependantDropdown.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Images/TypedSearchModule.gif diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Models/InitialSequentialCard.cs diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Program.cs b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Program.cs similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/Program.cs rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/Program.cs diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/README.md b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/README.md similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/README.md rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/README.md diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/TypeaheadSearch.csproj diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/color.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/manifest.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appPackage/outline.png diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/appsettings.json diff --git a/src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json b/src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json similarity index 100% rename from src/samples/ToMigrate/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json rename to src/samples/test-bots/Teams/bot-type-ahead-search-adaptive-cards/assets/sample.json From eb0cb1d9136ab0c2339ef05d9124feb019e90313 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 09:03:23 -0600 Subject: [PATCH 56/60] Added ConversationUpdateRouteAttribute.Selector handling --- .../App/ActivityRouteAttribute.cs | 2 +- .../App/ConversationUpdateRouteAttribute.cs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs index c89f1c13..fccda382 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ActivityRouteAttribute.cs @@ -10,7 +10,7 @@ namespace Microsoft.Agents.BotBuilder.App { /// /// Adds an AgentApplication.OnActivity route. - /// Only one of Type, Regex, or Selector will be used: + /// Only one of will be used: /// 1. Type /// 2. Regex /// 3. Selector diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs index 1dc225d4..00d22aea 100644 --- a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/ConversationUpdateRouteAttribute.cs @@ -1,16 +1,25 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.BotBuilder.Errors; using System; using System.Reflection; namespace Microsoft.Agents.BotBuilder.App { + /// + /// Adds an AgentApplication.OnConversation route. + /// Only one of will be used: + /// 1. Event + /// 3. Selector + /// [AttributeUsage(AttributeTargets.Method, Inherited = true)] public class ConversationUpdateRouteAttribute : Attribute, IRouteAttribute { public string Event { get; set; } - + + public string Selector { get; set; } + public ushort Rank { get; set; } = RouteRank.Unspecified; public void AddRoute(AgentApplication app, MethodInfo method) @@ -19,6 +28,22 @@ public void AddRoute(AgentApplication app, MethodInfo method) { app.OnConversationUpdate(Event, method.CreateDelegate(app), Rank); } + else if (!string.IsNullOrWhiteSpace(Selector)) + { + var selectorMethod = app.GetType().GetMethod(Selector, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + ?? throw Core.Errors.ExceptionHelper.GenerateException(ErrorHelper.AttributeSelectorNotFound, null); + + try + { + var delegateSelector = selectorMethod.CreateDelegate(app); + var delegateHandler = method.CreateDelegate(app); + app.OnActivity(delegateSelector, delegateHandler, rank: Rank); + } + catch (ArgumentException ex) + { + throw Core.Errors.ExceptionHelper.GenerateException(ErrorHelper.AttributeSelectorInvalid, ex); + } + } } } } From f7be574b94ab3f0a6ba47c65555db9cffd742eff Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 10:36:16 -0600 Subject: [PATCH 57/60] Added ABS AttachmentDownloader and test sample --- src/Microsoft.Agents.SDK.sln | 7 + .../App/AttachmentDownloader.cs | 97 ++++ .../IAttachments.cs | 9 + .../RestClients/AttachmentsRestClient.cs | 21 + .../Types}/AttachmentData.cs | 12 +- .../Types}/AttachmentInfo.cs | 2 +- .../Types}/AttachmentView.cs | 2 +- .../App/TeamsAttachmentDownloader.cs | 14 +- .../AspNetCore/ChannelApiController.cs | 1 + .../HandlingAttachments/AttachmentsBot.cs | 197 +++++++++ .../HandlingAttachments/BotController.cs | 25 ++ .../HandlingAttachmentsBot.csproj | 39 ++ .../test-bots/HandlingAttachments/Program.cs | 53 +++ .../Properties/launchSettings.TEMPLATE.json | 16 + .../Resources/agents-sdk.png | Bin 0 -> 109500 bytes .../Resources/architecture-resize.png | Bin 0 -> 137666 bytes .../HandlingAttachments/appsettings.json | 37 ++ .../HandlingAttachments/wwwroot/default.htm | 417 ++++++++++++++++++ .../AttachmentTests.cs | 3 +- 19 files changed, 936 insertions(+), 16 deletions(-) create mode 100644 src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AttachmentDownloader.cs rename src/libraries/{Core/Microsoft.Agents.Core/Models => Client/Microsoft.Agents.Connector/Types}/AttachmentData.cs (84%) rename src/libraries/{Core/Microsoft.Agents.Core/Models => Client/Microsoft.Agents.Connector/Types}/AttachmentInfo.cs (96%) rename src/libraries/{Core/Microsoft.Agents.Core/Models => Client/Microsoft.Agents.Connector/Types}/AttachmentView.cs (95%) create mode 100644 src/samples/test-bots/HandlingAttachments/AttachmentsBot.cs create mode 100644 src/samples/test-bots/HandlingAttachments/BotController.cs create mode 100644 src/samples/test-bots/HandlingAttachments/HandlingAttachmentsBot.csproj create mode 100644 src/samples/test-bots/HandlingAttachments/Program.cs create mode 100644 src/samples/test-bots/HandlingAttachments/Properties/launchSettings.TEMPLATE.json create mode 100644 src/samples/test-bots/HandlingAttachments/Resources/agents-sdk.png create mode 100644 src/samples/test-bots/HandlingAttachments/Resources/architecture-resize.png create mode 100644 src/samples/test-bots/HandlingAttachments/appsettings.json create mode 100644 src/samples/test-bots/HandlingAttachments/wwwroot/default.htm rename src/tests/{Microsoft.Agents.Model.Tests => Microsoft.Agents.Connector.Tests}/AttachmentTests.cs (97%) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 9f7c1356..a47b08af 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -114,6 +114,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBotCompat", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsConversationSsoQuickstart", "samples\Compat\TeamsConversationSsoQuickstart\TeamsConversationSsoQuickstart.csproj", "{B27560C2-0125-4775-807E-DA2F2E5D4B1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HandlingAttachmentsBot", "samples\test-bots\HandlingAttachments\HandlingAttachmentsBot.csproj", "{3E007AED-EDA2-4A15-8DBC-69F90CBF2551}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -280,6 +282,10 @@ Global {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.Build.0 = Release|Any CPU + {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -336,6 +342,7 @@ Global {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65} = {36494671-1A2D-47F9-B53D-354E0690DA82} {B27560C2-0125-4775-807E-DA2F2E5D4B1C} = {36494671-1A2D-47F9-B53D-354E0690DA82} + {3E007AED-EDA2-4A15-8DBC-69F90CBF2551} = {674A812C-7287-4883-97F9-697D83750648} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AttachmentDownloader.cs b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AttachmentDownloader.cs new file mode 100644 index 00000000..97cc434e --- /dev/null +++ b/src/libraries/BotBuilder/Microsoft.Agents.BotBuilder/App/AttachmentDownloader.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Agents.BotBuilder.App +{ + public class AttachmentDownloader : IInputFileDownloader + { + private readonly IHttpClientFactory _httpClientFactory; + + public AttachmentDownloader(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + } + + public async Task> DownloadFilesAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken = default) + { + if (string.Equals(Channels.Msteams, turnContext.Activity.ChannelId, StringComparison.OrdinalIgnoreCase)) + { + return []; + } + + if (turnContext.Activity.Attachments == null || turnContext.Activity.Attachments.Count == 0) + { + return []; + } + + List files = []; + + foreach (Attachment attachment in turnContext.Activity.Attachments) + { + InputFile? file = await DownloadFileAsync(attachment); + if (file != null) + { + files.Add(file); + } + } + + return files; + } + + private async Task DownloadFileAsync(Attachment attachment) + { + string? name = attachment.Name; + + using var httpClient = _httpClientFactory.CreateClient(nameof(AttachmentDownloader)); + + if (attachment.ContentUrl != null && (attachment.ContentUrl.StartsWith("https://") || attachment.ContentUrl.StartsWith("http://localhost"))) + { + // Determine where the file is hosted. + var remoteFileUrl = attachment.ContentUrl; + + using (HttpRequestMessage request = new(HttpMethod.Get, remoteFileUrl)) + { + HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); + + // Failed to download file + if (!response.IsSuccessStatusCode) + { + return null; + } + + // Convert to a buffer + byte[] content = await response.Content.ReadAsByteArrayAsync(); + + // Fixup content type + string contentType = response.Content.Headers.ContentType.MediaType; + if (contentType.StartsWith("image/")) + { + contentType = "image/png"; + } + + return new InputFile(new BinaryData(content), contentType) + { + ContentUrl = attachment.ContentUrl, + Filename = name + }; + } + } + else + { + return new InputFile(new BinaryData(attachment.Content), attachment.ContentType) + { + ContentUrl = attachment.ContentUrl, + Filename = name + }; + } + } + } +} diff --git a/src/libraries/Client/Microsoft.Agents.Connector/IAttachments.cs b/src/libraries/Client/Microsoft.Agents.Connector/IAttachments.cs index 81bca9f7..26330b1e 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/IAttachments.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/IAttachments.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; using System.IO; using System.Threading; @@ -13,6 +14,14 @@ namespace Microsoft.Agents.Connector /// public interface IAttachments { + /// + /// Get the URI of an attachment view. + /// + /// id of the attachment. + /// default is "original". + /// uri. + string GetAttachmentUri(string attachmentId, string viewId = "original"); + /// /// Get AttachmentInfo structure describing the attachment views. /// diff --git a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs index bf78ee05..7c118514 100644 --- a/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/RestClients/AttachmentsRestClient.cs @@ -7,6 +7,7 @@ using Microsoft.Agents.Core.Models; using Microsoft.Agents.Core.Serialization; using System; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Net.Http; using System.Threading; @@ -18,6 +19,26 @@ internal class AttachmentsRestClient(IRestTransport transport) : IAttachments { private readonly IRestTransport _transport = transport ?? throw new ArgumentNullException(nameof(_transport)); + /// + /// Get the URI of an attachment view. + /// + /// id of the attachment. + /// default is "original". + /// uri. +#pragma warning disable CA1055 // Uri return values should not be strings (we can't change this without breaking binary compat) + public string GetAttachmentUri(string attachmentId, string viewId = "original") +#pragma warning restore CA1055 // Uri return values should not be strings + { + ArgumentException.ThrowIfNullOrWhiteSpace(attachmentId); + + // Construct URL + var baseUrl = _transport.Endpoint.ToString(); + var url = new Uri(new Uri(baseUrl + (baseUrl.EndsWith("/", StringComparison.OrdinalIgnoreCase) ? string.Empty : "/", StringComparison.OrdinalIgnoreCase)), "v3/attachments/{attachmentId}/views/{viewId}").ToString(); + url = url.Replace("{attachmentId}", Uri.EscapeDataString(attachmentId)); + url = url.Replace("{viewId}", Uri.EscapeDataString(viewId)); + return url; + } + internal HttpRequestMessage CreateGetAttachmentInfoRequest(string attachmentId) { var request = new HttpRequestMessage(); diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentData.cs b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentData.cs similarity index 84% rename from src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentData.cs rename to src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentData.cs index 49b631d1..9470a9f0 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentData.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentData.cs @@ -3,9 +3,7 @@ #nullable disable -using System; - -namespace Microsoft.Agents.Core.Models +namespace Microsoft.Agents.Connector.Types { /// Attachment data. public class AttachmentData @@ -29,12 +27,12 @@ public AttachmentData(string type = default, string name = default, byte[] origi } /// Content-Type of the attachment. - public string Type { get; } + public string Type { get; set; } /// Name of the attachment. - public string Name { get; } + public string Name { get; set; } /// Attachment content. - public byte[] OriginalBase64 { get; } + public byte[] OriginalBase64 { get; set; } /// Attachment thumbnail. - public byte[] ThumbnailBase64 { get; } + public byte[] ThumbnailBase64 { get; set; } } } diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentInfo.cs b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentInfo.cs similarity index 96% rename from src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentInfo.cs rename to src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentInfo.cs index 6246d6c4..0e06b13f 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentInfo.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentInfo.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; -namespace Microsoft.Agents.Core.Models +namespace Microsoft.Agents.Connector.Types { /// Metadata for an attachment. public class AttachmentInfo diff --git a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentView.cs b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentView.cs similarity index 95% rename from src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentView.cs rename to src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentView.cs index 4b3a3999..d39ccdb8 100644 --- a/src/libraries/Core/Microsoft.Agents.Core/Models/AttachmentView.cs +++ b/src/libraries/Client/Microsoft.Agents.Connector/Types/AttachmentView.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Microsoft.Agents.Core.Models +namespace Microsoft.Agents.Connector.Types { /// Attachment View name and size. public class AttachmentView diff --git a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs index c29470c0..c434b320 100644 --- a/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs +++ b/src/libraries/Extensions/Microsoft.Agents.Extensions.Teams/App/TeamsAttachmentDownloader.cs @@ -22,7 +22,7 @@ namespace Microsoft.Agents.Extensions.Teams.App public class TeamsAttachmentDownloader : IInputFileDownloader { private readonly TeamsAttachmentDownloaderOptions _options; - private readonly HttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IAccessTokenProvider _accessTokenProvider; @@ -51,7 +51,7 @@ public TeamsAttachmentDownloader(TeamsAttachmentDownloaderOptions options, IConn throw new ArgumentException("TeamsAttachmentDownloader.TokenProviderName not found."); } - _httpClient = httpClientFactory.CreateClient(nameof(TeamsAttachmentDownloader)); + _httpClientFactory = httpClientFactory; } /// @@ -77,11 +77,11 @@ public async Task> DownloadFilesAsync(ITurnContext turnContext, accessToken = await _accessTokenProvider.GetAccessTokenAsync(BotClaims.GetTokenAudience(turnContext.Identity), _options.Scopes).ConfigureAwait(false); } - List files = new(); + List files = []; foreach (Attachment attachment in attachments) { - InputFile? file = await _DownloadFile(attachment, accessToken); + InputFile? file = await DownloadFileAsync(attachment, accessToken); if (file != null) { files.Add(file); @@ -92,10 +92,12 @@ public async Task> DownloadFilesAsync(ITurnContext turnContext, } - private async Task _DownloadFile(Attachment attachment, string accessToken) + private async Task DownloadFileAsync(Attachment attachment, string accessToken) { string? name = attachment.Name; + using var httpClient = _httpClientFactory.CreateClient(nameof(TeamsAttachmentDownloader)); + if (attachment.ContentUrl != null && (attachment.ContentUrl.StartsWith("https://") || attachment.ContentUrl.StartsWith("http://localhost"))) { // Get downloadable content link @@ -115,7 +117,7 @@ public async Task> DownloadFilesAsync(ITurnContext turnContext, { request.Headers.Add("Authorization", $"Bearer {accessToken}"); - HttpResponseMessage response = await _httpClient.SendAsync(request).ConfigureAwait(false); + HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); // Failed to download file if (!response.IsSuccessStatusCode) diff --git a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs index 8a8cb3f8..453db836 100644 --- a/src/libraries/Hosting/AspNetCore/ChannelApiController.cs +++ b/src/libraries/Hosting/AspNetCore/ChannelApiController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Agents.Core.Models; using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.Connector.Types; namespace Microsoft.Agents.Hosting.AspNetCore { diff --git a/src/samples/test-bots/HandlingAttachments/AttachmentsBot.cs b/src/samples/test-bots/HandlingAttachments/AttachmentsBot.cs new file mode 100644 index 00000000..4bfecf45 --- /dev/null +++ b/src/samples/test-bots/HandlingAttachments/AttachmentsBot.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Connector; +using Microsoft.Agents.Connector.Types; +using Microsoft.Agents.Core.Models; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace HandlingAttachmentsBot +{ + // Represents a bot that processes incoming activities. + // For each user interaction, an instance of this class is created and the OnTurnAsync method is called. + // This is a Transient lifetime service. Transient lifetime services are created + // each time they're requested. For each Activity received, a new instance of this + // class is created. Objects that are expensive to construct, or have a lifetime + // beyond the single turn, should be carefully managed. + + public class AttachmentsBot : AgentApplication + { + public AttachmentsBot(AgentApplicationOptions options) : base(options) + { + } + + [ConversationUpdateRoute(Event = ConversationUpdateEvents.MembersAdded)] + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (var member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync( + $"Welcome to AttachmentsBot {member.Name}." + + $" This bot will introduce you to Attachments." + + $" Please select an option", + cancellationToken: cancellationToken); + await DisplayOptionsAsync(turnContext, cancellationToken); + } + } + } + + [ActivityRoute(Type = ActivityTypes.Message, Rank = RouteRank.Last)] + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + var reply = await ProcessInput(turnContext, turnState, cancellationToken); + + // Respond to the user. + await turnContext.SendActivityAsync(reply, cancellationToken); + await DisplayOptionsAsync(turnContext, cancellationToken); + } + + private static async Task DisplayOptionsAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + // Create a HeroCard with options for the user to interact with the bot. + var card = new HeroCard + { + Text = "You can upload an image or select one of the following choices", + Buttons = + [ + // Note that some channels require different values to be used in order to get buttons to display text. + // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may + // need to provide a value for other parameters like 'text' or 'displayText'. + new CardAction(ActionTypes.ImBack, title: "1. Inline Attachment", value: "1"), + new CardAction(ActionTypes.ImBack, title: "2. Internet Attachment", value: "2"), + new CardAction(ActionTypes.ImBack, title: "3. Uploaded Attachment", value: "3"), + ], + }; + + var reply = MessageFactory.Attachment(card.ToAttachment()); + await turnContext.SendActivityAsync(reply, cancellationToken); + } + + // Given the input from the message, create the response. + private static async Task ProcessInput(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + IActivity reply; + + if (turnState.Temp.InputFiles.Any()) + { + reply = MessageFactory.Text($"There are {turnState.Temp.InputFiles.Count} attachments."); + } + else + { + // Send at attachment to the user. + reply = await HandleOutgoingAttachment(turnContext, turnContext.Activity, cancellationToken); + } + + return reply; + } + + // Returns a reply with the requested Attachment + private static async Task HandleOutgoingAttachment(ITurnContext turnContext, IActivity activity, CancellationToken cancellationToken) + { + // Look at the user input, and figure out what kind of attachment to send. + IActivity reply; + if (activity.Text.StartsWith('1')) + { + reply = MessageFactory.Text("This is an inline attachment."); + reply.Attachments = [GetInlineAttachment()]; + } + else if (activity.Text.StartsWith('2')) + { + reply = MessageFactory.Text("This is an attachment from a HTTP URL."); + reply.Attachments = [GetInternetAttachment()]; + } + else if (activity.Text.StartsWith('3')) + { + reply = MessageFactory.Text("This is an uploaded attachment."); + + // Get the uploaded attachment. + var uploadedAttachment = await UploadAttachmentAsync(turnContext, activity.ServiceUrl, activity.Conversation.Id, cancellationToken); + reply.Attachments = [uploadedAttachment]; + } + else + { + // The user did not enter input that this bot was built to handle. + reply = MessageFactory.Text("Your input was not recognized please try again."); + } + + return reply; + } + + // Creates an inline attachment sent from the bot to the user using a base64 string. + // Using a base64 string to send an attachment will not work on all channels. + // Additionally, some channels will only allow certain file types to be sent this way. + // For example a .png file may work but a .pdf file may not on some channels. + // Please consult the channel documentation for specifics. + private static Attachment GetInlineAttachment() + { + var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png"); + var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath)); + + return new Attachment + { + Name = @"Resources\architecture-resize.png", + ContentType = "image/png", + ContentUrl = $"data:image/png;base64,{imageData}", + }; + } + + // Creates an "Attachment" to be sent from the bot to the user from an uploaded file. + private static async Task UploadAttachmentAsync(ITurnContext turnContext, string serviceUrl, string conversationId, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(serviceUrl)) + { + throw new ArgumentNullException(nameof(serviceUrl)); + } + + if (string.IsNullOrWhiteSpace(conversationId)) + { + throw new ArgumentNullException(nameof(conversationId)); + } + + var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources", "agents-sdk.png"); + + var connector = turnContext.Services.Get(); + + // This only supports payloads smaller than 260k + var response = await connector.Conversations.UploadAttachmentAsync( + conversationId, + new AttachmentData + { + Name = @"Resources\agents-sdk.png", + OriginalBase64 = File.ReadAllBytes(imagePath), + Type = "image/png", + }, + cancellationToken); + + var attachmentUri = connector.Attachments.GetAttachmentUri(response.Id); + + return new Attachment + { + Name = @"Resources\agents-sdk.png", + ContentType = "image/png", + ContentUrl = attachmentUri, + }; + } + + // Creates an to be sent from the bot to the user from a HTTP URL. + private static Attachment GetInternetAttachment() + { + // ContentUrl must be HTTPS. + return new Attachment + { + Name = @"Resources\architecture-resize.png", + ContentType = "image/png", + ContentUrl = "https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png", + }; + } + } +} diff --git a/src/samples/test-bots/HandlingAttachments/BotController.cs b/src/samples/test-bots/HandlingAttachments/BotController.cs new file mode 100644 index 00000000..70a1ebb4 --- /dev/null +++ b/src/samples/test-bots/HandlingAttachments/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace EchoBot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/test-bots/HandlingAttachments/HandlingAttachmentsBot.csproj b/src/samples/test-bots/HandlingAttachments/HandlingAttachmentsBot.csproj new file mode 100644 index 00000000..661c7754 --- /dev/null +++ b/src/samples/test-bots/HandlingAttachments/HandlingAttachmentsBot.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + latest + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + Always + true + PreserveNewest + + + + + + Never + true + Never + + + PreserveNewest + + + PreserveNewest + + + diff --git a/src/samples/test-bots/HandlingAttachments/Program.cs b/src/samples/test-bots/HandlingAttachments/Program.cs new file mode 100644 index 00000000..1fe2ec19 --- /dev/null +++ b/src/samples/test-bots/HandlingAttachments/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using HandlingAttachmentsBot; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Net.Http; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add ApplicationOptions +builder.Services.AddTransient(sp => +{ + return new AgentApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()), + FileDownloaders = [ new AttachmentDownloader(sp.GetService()) ] + }; +}); + +// Add the bot (which is transient) +builder.AddBot(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/src/samples/test-bots/HandlingAttachments/Properties/launchSettings.TEMPLATE.json b/src/samples/test-bots/HandlingAttachments/Properties/launchSettings.TEMPLATE.json new file mode 100644 index 00000000..38bfefde --- /dev/null +++ b/src/samples/test-bots/HandlingAttachments/Properties/launchSettings.TEMPLATE.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "EchoBot": { + "commandName": "Project", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "Connections__BotServiceConnection__Settings__TenantId": "<>", + "Connections__BotServiceConnection__Settings__ClientId": "<>", + "Connections__BotServiceConnection__Settings__AuthorityEndpoint": "https://login.microsoftonline.com/<>", + "Connections__BotServiceConnection__Settings__ClientSecret": "<>" + }, + "applicationUrl": "https://localhost:65284;http://localhost:3978" + } + } +} \ No newline at end of file diff --git a/src/samples/test-bots/HandlingAttachments/Resources/agents-sdk.png b/src/samples/test-bots/HandlingAttachments/Resources/agents-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..363d24ee0651e2cabf8c550c2771babd07ae80ff GIT binary patch literal 109500 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

    paC55m_>ryRNvbU22jABe5< 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 zak + + + + + + Handling Attachments Sample + + + + + +

    +
    +
    +
    Handling Attachments Sample
    +
    +
    +
    +
    +
    Your bot is ready!
    +
    You can test your bot in the Bot Framework Emulator
    + by connecting to http://localhost:3978/api/messages.
    +
    +
    Visit Azure + Bot Service to register your bot and add it to
    + various channels. The bot's endpoint URL typically looks + like this:
    +
    https://your_bots_hostname/api/messages
    +
    +
    +
    +
    + +
    + + diff --git a/src/tests/Microsoft.Agents.Model.Tests/AttachmentTests.cs b/src/tests/Microsoft.Agents.Connector.Tests/AttachmentTests.cs similarity index 97% rename from src/tests/Microsoft.Agents.Model.Tests/AttachmentTests.cs rename to src/tests/Microsoft.Agents.Connector.Tests/AttachmentTests.cs index 56b72951..6e571d8e 100644 --- a/src/tests/Microsoft.Agents.Model.Tests/AttachmentTests.cs +++ b/src/tests/Microsoft.Agents.Connector.Tests/AttachmentTests.cs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Agents.Connector.Types; using Microsoft.Agents.Core.Models; using System.Collections.Generic; using Xunit; -namespace Microsoft.Agents.Model.Tests +namespace Microsoft.Agents.Core.Connector.Tests { public class AttachmentTests { From cbdb7e929b75fe55328325212c15baf8582131a4 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 10:45:28 -0600 Subject: [PATCH 58/60] Removed test-bot from Solution --- src/Microsoft.Agents.SDK.sln | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index a47b08af..9f7c1356 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -114,8 +114,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBotCompat", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsConversationSsoQuickstart", "samples\Compat\TeamsConversationSsoQuickstart\TeamsConversationSsoQuickstart.csproj", "{B27560C2-0125-4775-807E-DA2F2E5D4B1C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HandlingAttachmentsBot", "samples\test-bots\HandlingAttachments\HandlingAttachmentsBot.csproj", "{3E007AED-EDA2-4A15-8DBC-69F90CBF2551}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -282,10 +280,6 @@ Global {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.Build.0 = Release|Any CPU - {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3E007AED-EDA2-4A15-8DBC-69F90CBF2551}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -342,7 +336,6 @@ Global {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65} = {36494671-1A2D-47F9-B53D-354E0690DA82} {B27560C2-0125-4775-807E-DA2F2E5D4B1C} = {36494671-1A2D-47F9-B53D-354E0690DA82} - {3E007AED-EDA2-4A15-8DBC-69F90CBF2551} = {674A812C-7287-4883-97F9-697D83750648} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} From 0e41db6b66c5804d51b5faaa15bb90d396d91978 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 12:49:31 -0600 Subject: [PATCH 59/60] Initial StreamingResponse test-bot --- Directory.Packages.props | 3 + src/Microsoft.Agents.SDK.sln | 7 ++ .../IntermediateMessagesBot/BotController.cs | 25 +++++ .../IntermediateMessagesBot.csproj | 32 +++++++ .../IntermediateMessagesBot/MyBot.cs | 60 ++++++++++++ .../IntermediateMessagesBot/Program.cs | 62 ++++++++++++ .../IntermediateMessagesBot/README.md | 96 +++++++++++++++++++ .../IntermediateMessagesBot/appsettings.json | 41 ++++++++ 8 files changed, 326 insertions(+) create mode 100644 src/samples/test-bots/IntermediateMessagesBot/BotController.cs create mode 100644 src/samples/test-bots/IntermediateMessagesBot/IntermediateMessagesBot.csproj create mode 100644 src/samples/test-bots/IntermediateMessagesBot/MyBot.cs create mode 100644 src/samples/test-bots/IntermediateMessagesBot/Program.cs create mode 100644 src/samples/test-bots/IntermediateMessagesBot/README.md create mode 100644 src/samples/test-bots/IntermediateMessagesBot/appsettings.json diff --git a/Directory.Packages.props b/Directory.Packages.props index ff246e11..eee2a455 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -90,5 +90,8 @@ + + + \ No newline at end of file diff --git a/src/Microsoft.Agents.SDK.sln b/src/Microsoft.Agents.SDK.sln index 9f7c1356..c6eed4f4 100644 --- a/src/Microsoft.Agents.SDK.sln +++ b/src/Microsoft.Agents.SDK.sln @@ -114,6 +114,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationBotCompat", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsConversationSsoQuickstart", "samples\Compat\TeamsConversationSsoQuickstart\TeamsConversationSsoQuickstart.csproj", "{B27560C2-0125-4775-807E-DA2F2E5D4B1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntermediateMessagesBot", "samples\test-bots\IntermediateMessagesBot\IntermediateMessagesBot.csproj", "{20CE5684-E4C4-4815-A676-132C479685AF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -280,6 +282,10 @@ Global {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B27560C2-0125-4775-807E-DA2F2E5D4B1C}.Release|Any CPU.Build.0 = Release|Any CPU + {20CE5684-E4C4-4815-A676-132C479685AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20CE5684-E4C4-4815-A676-132C479685AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20CE5684-E4C4-4815-A676-132C479685AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20CE5684-E4C4-4815-A676-132C479685AF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -336,6 +342,7 @@ Global {A2C3344E-80B0-48B5-9828-45DC5CE7BD3C} = {295CD61D-DB20-4DF5-A917-2665DB79A6E4} {B6D4A5EF-5476-4B2C-BE07-6ABEDEA51B65} = {36494671-1A2D-47F9-B53D-354E0690DA82} {B27560C2-0125-4775-807E-DA2F2E5D4B1C} = {36494671-1A2D-47F9-B53D-354E0690DA82} + {20CE5684-E4C4-4815-A676-132C479685AF} = {674A812C-7287-4883-97F9-697D83750648} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1E8E538-309A-46F8-9CE7-AEC6589FAE60} diff --git a/src/samples/test-bots/IntermediateMessagesBot/BotController.cs b/src/samples/test-bots/IntermediateMessagesBot/BotController.cs new file mode 100644 index 00000000..5a48d867 --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace IntermediateMessagesBot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/src/samples/test-bots/IntermediateMessagesBot/IntermediateMessagesBot.csproj b/src/samples/test-bots/IntermediateMessagesBot/IntermediateMessagesBot.csproj new file mode 100644 index 00000000..7ec154bd --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/IntermediateMessagesBot.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + latest + disable + + + + + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/samples/test-bots/IntermediateMessagesBot/MyBot.cs b/src/samples/test-bots/IntermediateMessagesBot/MyBot.cs new file mode 100644 index 00000000..89d70ce7 --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/MyBot.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.Extensions.AI; +using System.IO; +using System; + +namespace IntermediateMessagesBot +{ + public class MyBot : AgentApplication + { + private readonly IChatClient _chatClient; + + public MyBot(AgentApplicationOptions options, IChatClient chatClient) : base(options) + { + _chatClient = chatClient; + + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + } + + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + StreamingResponse response = new StreamingResponse(turnContext); + + await foreach (StreamingChatCompletionUpdate update in _chatClient.CompleteStreamingAsync( + "Write a poem about why Microsoft Agents SDK is so great.", + new ChatOptions { MaxOutputTokens = 1000 }, + cancellationToken: cancellationToken)) + { + var text = update.ToString(); + if (!string.IsNullOrEmpty(text)) + { + response.QueueTextChunk(text); + } + } + + await response.EndStream(); + } + } +} diff --git a/src/samples/test-bots/IntermediateMessagesBot/Program.cs b/src/samples/test-bots/IntermediateMessagesBot/Program.cs new file mode 100644 index 00000000..b6534118 --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/Program.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using IntermediateMessagesBot; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Azure.AI.OpenAI; +using Azure.Identity; +using System; +using System.ClientModel; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add ApplicationOptions +builder.Services.AddTransient(sp => +{ + return new AgentApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; +}); + +builder.Services.AddTransient(sp => +{ + return new AzureOpenAIClient(new Uri(builder.Configuration["Endpoint"]), new ApiKeyCredential(builder.Configuration["OpenAIKey"])) + .AsChatClient(builder.Configuration["ModelName"]); +}); + +// Add the bot (which is transient) +builder.AddBot(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/src/samples/test-bots/IntermediateMessagesBot/README.md b/src/samples/test-bots/IntermediateMessagesBot/README.md new file mode 100644 index 00000000..922c8aea --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/README.md @@ -0,0 +1,96 @@ +# EchoBot Sample + +This is a sample of a simple Agent that is hosted on an Asp.net core web service. This Agent is configured to accept a request and echo the text of the request back to the caller. + +This Agent Sample is intended to introduce you the basic operation of the Microsoft 365 Agents SDK messaging loop. It can also be used as a the base for a custom Agent that you choose to develop. + +## Prerequisites + +- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 +- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) +- [Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) for Testing Web Chat. + +## Running this sample + +**To run the sample connected to Azure Bot Service, the following additional tools are required:** + +- Access to an Azure Subscription with access to preform the following tasks: + - Create and configure Entra ID Application Identities + - Create and configure an [Azure Bot Service](https://aka.ms/AgentsSDK-CreateBot) for your bot + - Create and configure an [Azure App Service](https://learn.microsoft.com/azure/app-service/) to deploy your bot on to. + - A tunneling tool to allow for local development and debugging should you wish to do local development whilst connected to a external client such as Microsoft Teams. + +## Getting Started with EchoBot Sample + +Read more about [Running an Agent](../../../docs/HowTo/running-an-agent.md) + +### QuickStart using Bot Framework Emulator + +1. Open the Echobot Sample in Visual Studio 2022 +1. Run it in Debug Mode (F5) +1. A blank web page will open, note down the URL which should be similar too `https://localhost:65349/` +1. Open the [BotFramework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) + 1. Click **Open Bot** + 1. In the bot URL field input the URL you noted down from the web page and add /api/messages to it. It should appear similar to `https://localhost:65349/api/messages` + 1. Click **Connect** + +If all is working correctly, the Bot Emulator should show you a Web Chat experience with the words **"Hello and Welcome!"** + +If you type a message and hit enter, or the send arrow, your messages should be returned to you with **Echo:your message** + +### QuickStart using WebChat + +1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot) + - Record the Application ID, the Tenant ID, and the Client Secret for use below + +1. Configuring the token connection in the Agent settings + > The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) + + 1. Open the `appsettings.json` file in the root of the sample project. + + 1. Find the section labeled `Connections`, it should appear similar to this: + + ```json + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + ``` + + 1. Replace all **{{ClientId}}** with the AppId of the bot. + 1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered. + 1. Set the **ClientSecret** to the Secret that was created for your identity. + + > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. + +1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and use that port number in the devtunnel command (instead of 3978). + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + +1. On the Azure Bot, select **Settings**, then **Configuration**, and update the **Messaging endpoint** to `{tunnel-url}/api/messages` + +1. Start the Agent in Visual Studio + +1. Select **Test in WebChat** on the Azure Bot + +## Further reading +To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. \ No newline at end of file diff --git a/src/samples/test-bots/IntermediateMessagesBot/appsettings.json b/src/samples/test-bots/IntermediateMessagesBot/appsettings.json new file mode 100644 index 00000000..60dcce17 --- /dev/null +++ b/src/samples/test-bots/IntermediateMessagesBot/appsettings.json @@ -0,0 +1,41 @@ +{ + "Endpoint": "", + "ModelName": null, + "OpenAIKey": null, + + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file From 42cc2e14d8bc68ad93bc89d9521399ab2ba5b74c Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 3 Mar 2025 13:25:43 -0600 Subject: [PATCH 60/60] Minor prompt change in AuthenticationBot --- src/samples/AuthenticationBot/AuthBot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/samples/AuthenticationBot/AuthBot.cs b/src/samples/AuthenticationBot/AuthBot.cs index f6bf433f..0b799eda 100644 --- a/src/samples/AuthenticationBot/AuthBot.cs +++ b/src/samples/AuthenticationBot/AuthBot.cs @@ -67,7 +67,7 @@ protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnSta { if (turnContext.Activity.Text == "auto") { - await turnContext.SendActivityAsync($"Successfully logged in to '{Authentication.Default}', token length: {turnState.Temp.AuthTokens[Authentication.Default].Length}", cancellationToken: cancellationToken); + await turnContext.SendActivityAsync($"Auto Sign In: Successfully logged in to '{Authentication.Default}', token length: {turnState.Temp.AuthTokens[Authentication.Default].Length}", cancellationToken: cancellationToken); } else {

    ?M0IJ}1$K9|k^_+JyegjcsWrc5zWo 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 v6>x#ZRx_YOAr-8-0O1UTp?w5DGhp&J+% zRT=Sj)f2>jp*tTe#T3Qfy{n5wdN%$D-A8o%s_pXb9cu5t7Yq)a+>dwf-il-;#ne3w zPO{;Rum|u4yKj^bI`21p&O7DQ3VCo~K4Pn=KkF(N(ve7hux6Mklud*l6GE56obj1_ z0rPmk%OD7ezQ3NO_GoBmZ}4dcY_wck41&l-3#P3&38SU|ZE8psio5^&{lAUZ_*9_s z|2b};)<{10pD$-DVlyFd{pZ{JTvABl|NAokb*kd@|3CHL(EY!M>4J-rn;SQkSzp}q zdm8=TJUKrp|^wcl=P%YSByd^AQPmGxWS$<_!i`vhb8=&)avq-|2guGj$A^fQg(J zFO1kvd=85*UBs`{ulh}&hGNKuI=rsg9H+>?JRGB`RX3v44CUwxD1vjyT3%o;9#aiX zwu8fYowj*V$@rt{L|zJy#_*)#5PDIFxVQgw(RO=qX5fwAWw%Ri`bJpK7Mq-J2rab$ z$hzLP;C;Do)bzT~%t5!_bzY;Im4@wuH{;Of@}=2fJ%&V}nTmk{lvFN@%7mbn7DECK zY_R2jz8uOe#-@tIW%=3AK*jHIW+NvHxfmY}4o`GEnqyM~js9eUt-+~TeYD{mi9^pN zPxeFB;j|3|`$H6o3#{?4MoO-HzwgjuU`?yU7iYzWCO2`t8nogIG&`^t38q;&u@@_5 z#sp|F%T%O{vwB^bJwF}0-nO`RKJofL@2&JFZO3-w8ts7s6rcl8p;^;2&|o>$X^S`Z zY)?}>+VpJm@D@j<#oSSfU)=lgYAdx&?c&sfN`anG@o2aIFNev_AmwLXbVI*e>SD$0 z;+*u!Zv2=J+UIV!5o|lQgBfnetd@6QW_hR_s+sXQyg$<@W_;3G#KW?W)2w^c{I8io zASAeXOcth?$&=^&SU;RXMa%4E(6@`YGhHvG)@ZY?xrc-ltDGl26hqQCU!k>iXcX#- zJN|6K%ccVRC?f~{UE1AGbtH#7lc_8x1iHa0ZIk#h&ZikAYgBiRsbI|l zS*8!6A;!xy)TJs_44~v1^1CTS=tTWtXheQ@vJuS#_Qjf*m?&OrB#K|6o}8kutH{aG z+GL(Ah>VQX!NVj3&RQz%T;v7SlgZR6YVSVYOD$CTTAa05W)8T&@&QOQxa`A>bJ0TE z+uOb2IKrYU^($z~wQ3N3e0fwA3lBv6A~)O**oTOFo87N ztFm~?bw%ATeVMFS+}!a~OO9nqXnf(RHk8N_^g3@43BQUvPd>O0ThhC$ySw?RU1m4pRc9U(69WRyp4C-C?$3idgM=cmnUT+RH-^a}tf>Ob!Uj!pS#i#ex|VGY zDS0+}-2rbfe4)s#d#6EFsyr%xEJstulXjQ`SPUY9(-7KHa{I(478e$9AFmH2+Uobt zeBRs!X0cYA0YiK8sr?2+cSS_)y!bh|hwj zYT8OQ=Ub>>%y2Eb@Add%sY*d}#FKNahv?7ukJI^5NV{Xn$1&f~F$n10jaZbjcq#nn zsWGIORnNhl0eu6^GqIzYJS+~7M@AyAt-wn!C0gzT&qyXgnn~HaljZYEt|dChi$7vs z7ItRc(qJ0i&)AUubAB5CIcOXX04>^va5vBew!I$N>&-?e_wobW@d*YT_#>H6amk!x zlahrU(y|P3>s(TX+PA$lavJnO8xN3mToIm@D=n+zi5wxuJ4hTqczYmn>&{+;8t$=U zwu@$PXnhA3L!|p1aa=w0Nn$Q`R!5>Zwhu|e!NG;U`N*9(D|s4RV+PppFb~%?SWdbR z;rS;|`61-OE&?{hTdLQmYQ^k*Uv51fta~LZ4V&IK96ZN#Jbv2lFPlx{cVnoIL~pEb z77q*)Au1b#biXm{v{={N#gmHzrZ!`xBzcW?V@1k1oeziEd?B}551tqAIPKQn{}dBD zoIN0!!;JS(VyGIWe9vy?IFuW}^g|8va>whI9}eZ|-R;prO6UP)$3R^e7K|AH5J@W3 zUSK*jQJnP$G;ezW86&U}BKryq-JeLt>Jui3Frk*;xo^9!v%92p60e-gbQ6xFj=&pi z0sGOS&2{F7eu!MczkIppWs(ku>^xo?zgVB(@3MTy$741a+#N|QOz5`q{)3hL<7B>o zapP?5G^WAj^vNxXl#4L6-)MPdncgx_Iy!@wZbCCRXML*pMYq_O=VJbX1p|MxCYU3{ z_0CTFa(vOSKs0X`a{mGDqv=iBG%vNeS<$yX6HD6?kZQi96Aoz#oq5LPszRlHr%u18 z;OaDkMMtht%iZ=l$Mq0OmRp?W{l=XCMN#8qy4%!BBSq~vFR70&_Ipy-^Z)%76Ohq zquSom=>n&RO_)a}?K@tLSHNypg?2^Eia~+bvL4RXT$Kwg@*#xzx9hpJ+(&7UOTGP$ z_j7~Y$C+?ts?ZqXfh2mh&0|0L<=ocR?@sP(7iR)~Qi&wTL|bfc0@Vo*O1pZuX^CJ9@d`%jELmUU+SnvrBGTVnaQ{36;YE z?KBwY_D}^~nu9ak-i!xX)TWN2xyudBBt2W5o3D1qe#)BQ&9)I4O1}vD*Dp==m1?&$ zr@S<%cP@p;TV~$91-wC?l$JVTdF2ZNh=VvvzA;RX*3!en3;42~sHvA}s_ju+zdmC{ zz8TPQGmDJ{FFDt_y};i{RCjz#syaXtfW=HEX>4j5{ANOSjYAg|xjm(r!K+O?)WBdS zLP4Rju=>mPpk=y9*JHT>=Y65W(M%au2Dg2A|FMSCegqC<6aj|?0})^(%?YdS42*+6 zbuQ$3lH`kx5|H}0{&Xcks~j8}94fP!ExbNmRB?H4De{LR2tH3?xt`bi`Llz!K2BX0 zmJrwrtsvk*<>dTbBWGt4H3XG3;pryeaJ5}v#p5I}rxWtabIbSPd9+56&vTsCXsEf0 zp>T=(ZF#f~>~~MnFK<`?|FyF>d*cX6Y_n1#B(M{2xu#r7G%PR zS1Hx%Ce7fkj0MO<#aV7Sbmk_hSx$^_2*3$}&mp11!~1lsmf)>4`l#*k`5z&} zp*_DNyE%{5c&i3>cjwj%5zLefU2NqRWG%J4aXSq1yb;A*io}XV>$uIkn;qo%nWzc( z4WGY;{zf6(=~xQE0Q*OhV;D6GV0%7FS@~11?vEsXv*$RE{dk336ZbOL`@6{&gP1rN zAhonmsrK#UvONp|2`hI*WhQ94)L7s5b>I!1{`Fu|OKF$%Ze|Xljk9t9lZ@!%ttYi=ZX{hVFZis9342*I-Ppk=1lZ zneqAx7ts9_%&CCSBV8mjG)TDN-QG~x#C5XPHBX)#wKbDm0YnofHbWoUaJ;#oNM>Q9 z=$SeXf&sD!LnQrpsA8h?ysr0wC~s9NU@3IM6^}!sQniv8vS#q>4 z9YFYj28+a~W)+It@d8&A8P5Xx;W?mnZ^zbZy|^mR?N}4YsKs378ycI-s$OBxcX25b zglw=Vs6pUyvcfcEZ+-Zn=`#{<6c!W1mc{P|Qf_xA=>sF}6FkGdNABwCQgtY6PBh*< ztOF9xR}kScKg^=zU|6?$jjm3$7zXsk*z3+c<#dAC8m*?sYW>#<-2|)OT$k|b6}kEr zn26L#um0j0657m{n}2V&XriE`T&s1u#+9gAX+j>);Nq&(YnI%@I2fAhv;+k4(!x1~ zsjvLyI6$^vZQ^h}UeWgZ76Y~z=jODYS*kTJxbz(Qmc!oYy@S#hOV!O2_DkJbKDCN< zuEG#}al6=uVr)zS94@8nE9bIB(J0sCLMFNkr=Xxv+9MJ$&d*DE&dKC?E%8TU+Xmk6 z`eyRjnzXu~)`K{_j#z;FO}2#nDH+=_?IqNg+Vxim~2*}r9SuRMqkGw;x$eqMuxZ!x!kC9AE$v$|Y zIn=UyUtxbZTT-$$QzQ?Bg-0uO1HW7pP<#UF3_b zIa!D%k9m;Yj^;U{Al6a81158XY#;n4^dt&s6p`gqzi{rc<99f$ak~Cp7@sOPN2-f0 zndfj)Z1!t+!ILPEMo}l+68`OWZvEhOg9b+i0QB7+8-P%0huY8Yd0cTJn1q;Sa=dGm7f#xMIwm#Uk6^rFJ-EJ%+c$!8%?bu{JL`wz*hdrC z9h3m(hgIb=XPmyMziJ9?$iQ4$1(oKUgL;p1hdOy<;Iv3AhdK_V!S3(6%j;1l00lbx zIEh8vC6{x5{2-S|Jrgo(-v>I@>6%?HoC3YCvI3s|q6c+@?Iq023XRu-N8m8pKo)DQ z)*TLgKz};~)K8piN#~a)ncAT!4;K;=eZX530S`M4Nhv9!=R#(Ji!GzNZ*Is$Je6t? z%k{ZNZ5KXi{XQ*2KWpBA*DPajm!FNk>O@wzk=0sbS^cTVGpqPZimY~{U0a@ikT=k< zSSbY%4y?U!tjnA2uTJJTS#OZvLqz_>?Rq{vs|+2?KA9kR!if=o)Cm3Swo^;1#VtjN zw|lV0<5*NC>&TS4-7B;~N3LXRA3Yaw?&ldy?%jBSM@eSXV2^q@BXWy|_E*SQG>Yuc z-zGwd^lKlW~e66 zs^_4sv_czJd?jL$@|;;B9sq&&jn~kWsBfC73|hJGjZ4Oei%z?eecv2?Vv=E#f1k7j zmKUkd?MeQXUh1h1^n;7&zg;}n4T2(WX6q>UpmWLbWQi>(S5Q~2_#027!#v3GvX?J8R(Q2|T-xx<_mefc zO9~!`?<}wSQ4cKfz`q<81m)rDy4`1n$wOMtNff!hCWp+zIW_l+mlOaHpG>)%gz)3s z)P45G5yvH#&v*TtX^*EH?kph}o#oXXS|x&&=5r$}aCOxZKT9&;G$1pnoA&+M#bkoi z5rJW|b8=q&gO*4vNzqk@+~K0uWOt(4t+R`Vmy;7~XT2Et_j%_(EOPG5|d4L`i5 zqd7GIKhpkWzX|1T)nBLcgO%ebY^bZb|v1)Taa(wu~lp}k2*BKoIxyyIn`{dc~qQ%QEKp}Hy zm&W#jH&?Aob^xtD%dm!~3Ve7t?H9yJVvj@7-rl1sF)y_m7p%nAuz@|e z4By|~5^-sQN0S&3orXVmcN2QWJl`&Y=~U@NfB|g8)zyDDcMroBm@*1vuTyxvwmJLL zC>Ur^SGfVIi|JKApPBIeFPCeL!y@5h$b`!8aIo;;Z5fH?1BbzlU-JU3l)K|A+ujH< zFMk?QE#4pG4wk3z1HbsPn}vOnxV`D9yb3e!6Om8N*IQOSoQLQq4^2#l|EY&Nk|3ropAH$==CDD*lrLj?|&2D;VnM=vZ`QB0~ zSJRqsTwaFMquB_sQm>eE`-(5<$qHSRkS^=k7#MyqLClVuPcPcsunc`K%a(>PkJ5(| z8B3Nrd#=crjBdN%%6*}Ws>)PHlx*fbHr3?z(R_u9 zw4F;g3|To$=gGe=!kL@IpqBhm#x6=*~5!DacTgejg&akvQ)U=k-dQ_Aj)T+_M6U zBkea@-pD5+NDikv8ExjPkz{1x%@Tg^MsDTGcOfs;PTQGQ&gpspZ|ZOBzsBdIZEA+0%g{4Wyf~<<{CYZjihfmDRgRiok-mWrj zpOR~hUkV`kHk{w&?CcsJP&647u8pUdH|}oZExpm@Qt3-_X0!O}O&?PGj2Ydn_9Bu8 zqv~tuF5kRekAtB_RN-@ZaWqjG-3LQMk!0#rJFO~dloJ6*^u=m@4Qu~xJhruZoq4&h zw39DC`$7j|1!7|1(8Tj72h?+BsO7iETiQ2I9+57(%p1Q2S0%qdx=VC6Kq*WW;(UF* zlp5H{ot@LLF zxdpqonR1MuW36?JXk_NS3Tsl4YSGm;F@9eGX_T03w2A7YPA_Oyn)t_$KjgRBe(=`^ zqevy6`~2`y*=fUKafq?YyVR7Y;*GIh`Y!G+#XKcH*vywY$hDR4bqdI(vPbZ6 zI8;{;lLIL~e-ZaXLqtl1XT^X5^%~&u-H~kF@UOt@xNAIS`VzoFb?1XrKdQul|I4iw z@mjkU;?NZOju#Dewl2T-;g!KI!Zl8 z4LaJ1-F27C-~MVL0qZxH15pGXqpy?O10X9nI6_8*==~z9!Kg{yYno`*fLF33ZOB$A z+5Bh%nM`y~!T^$bbH(-_&%$r9`BHMF=N4Cy?_8bSB;rQS1|rEfIV^A!(1IpD<|c+Z zT`w@r!*`gKwZCMH*9Pc)H$%>+ih>1Cx${K?sVet}60F#)NQvAE5|N-qkSt(@Bm{SI z_~uY#^hd@-WWks0q_ddPSjdQ!4@Z0T3E_*-F zUbm(6yks&*BiL`ukc#2>TQ9P0_^X#(4SGV_~`D>WP@1^Gl#k7$FVV_l6a@c}z#+I*-KCTZc>Lzuvt!6hsd-rZ8%3e=F z^S=JrGL378Lw;*$_jMG&dva}@Mm_nEJesOt8^s!Rdsw%V#$m~9F`g0fc)eeScbak+ zp`RLs0(&n!D$D0{hU~mILhb%g-AOHlgeRFFHdKqg(=gh}`ngLo+~*kej?0YqOFLYr zoR!!EahY}}5R-<>c8;}(^;JNjKehAdT^2Uue7OgJIz=5%r)#fvSM2Xdy!xL@dn{xV zv)!|AaUqloJ>s9!wt|Ls@W-e1N!`qZhq1-cGR^{DAP-a2`jhahgDFFt{w1ZH_?WxK z9a;}FOW%7zX&3f_E#_^@mTZPyYlrJ4En#C2crJ3^L)iC0Y&VH{l*Rr72YtY;s{t32 zQk~MNqo9EH&&{hBa}LF1mb7+qDHa=}h`%!>5+QY^@f8a_lbd!-4wN|R0H&lB;w zKG3ETh+^lw$6l1!DVsLhgk4mv9&uGbeTq$dn#)5a1$hdBPp zjv_tVW~Bw&q?5x(L`Nn+t)%co(BCrGsp4Je)C%*YhDOePbw=fyeuVL(;L5Q4=?B@~ zeMCX*TJ_E$1}!E*QeYXhdv%lIrH_I>BC4M2icbb!S6+QDG z`-DgsGfjBdXNdwb#fQ&(}5FeLWCtrsV zkL6juYA3SDX@}P6aEfB7-7=M5)6;XwuUYr>_EjGYdr-6Ia!fv1b(Q$#S0|TnUkZea zPQU@N2Bk*cm>(6cO-%@5D+>9EFzndwTD! z_49T#Koncn!FzP|zDN0n;0#NdB5$i6`~l;Ol8NY#o3p=L2Oa_6sb#iz;!hHJHD$q- zG$93#%gcO!MvpV-_Y9o($LLL1h`WUENfS#zJcq;|lMA(&erdM4&*Rq{4H{8veFqtJ zeg60fTe4Aa)ZJdEAmHuwVKWGAZl=aiq~rd~=RRuRW@M$wR=G`_ukrW-moh(Jxy?js zEQ7{YX#xSfo$FnDuVtV*@L49g>=D-SI9Zo<;4GGL#B#< zu*GZNaLiXAeT2IEBZ|diebE`CKsO$f_jx9J^uBpwnj|sb%Jp7C5byP%To#q{jMrwV zYl@_#BvGGyHj{zmv;l2h+ei-rTELsA9S5JxNRLYRW5Vd-eG2^^b5{R`Gy^;ld?L9| z7$TSOvc)mYci+bT6VvHe$I$r-4RH}9B2nFk4IjUFMh}CshpKt2eBq6a!D!NV9_csU zW|JBE(Pv&SgPnI-EUzX7l=G3TK3<+Lam23pL|it%B)Q-r`Hn8`&SHa6q_QUo)76XI z%S{5G)oA%5P4x;an6miX;7Y#b{^@~*S8kHS;c_-{o-#HYb#^o&-`+V#L)k&UOXsu6 zqcEsG_PI|AZ*Eb;gGwssznu{;$1(d!pNKdr`xe7PJ}nO{MukTXoURH(XD|MsB{IgkxAjd?ObU(?hg5f{j5ow^(gHwb2PVnvc#aCg3Ozjm#u! zwgbZi{k6v(7+tC}HEZW>qZwRn`?@9W=;(}HQ2*VpWmgO7N`y{$S$p z;cj&}^?oBRP`v)!bVzA?cnh`|dne1xRj zWX#AU;W9p>mTtbQUq5=@ z|EhM(W2Zj2o={s00S`lAES$O^S=}J_la&xr3|p@&e3>7nr9Pf!oSK#2tGhC_Z_gPd zrGoR6NKA*x&+YGwjmZQh5>na=Ic;Y?oRJb-3_XiDJx%}Mi=S^kLD`fo;wY0qQ%PPLtrf{b(^sb!K zS=R0mil&ax?zkxVI9=~|=GVD~Z-W-9b*xX$9V}*x6>YG!Tts?k>u|FAriqu0E!KbE z%Ei?IXz9P~2{~dM@5r97#4;N`SgLNGRgw;X>aX(>XrTdK_~$4U%ZEL9&- z<#oFwjv?hYliNG@&_63OuT6$jOI~g}ck89vS`oiYA)k6y(%VQB$XfFfvdJ{$3Odyj znlC9>S>anurbqU{--BGfYe&QF`Eu}DQdP~097o^B*g{W6B_r^RQr2B|w` zY%4cdFwGMF!afa*7Q&^j6R@M+r8`LZd@FDm;ABJ0L7bSc7ZSmMDc%GG22agJ0<@-* zV3)eje|(Z?!p*b?a)+wPuKgvve#a}7z8IakT~NhSgs=T@4?bGjV@%>H>M%I;Ldd16 zc}<_h`vrv^koi4=XVWiD3(Js(8}?IKIW3 z8nnBkR4i5w45A!EF=$0TcRbdlGUYtVr7jovg=+24imZgq=Z_fqvyPW1)*aUlEb7&E z*jB{KA*;EGpRQ*HA`{LzK}pOgnVohnqiG%bt0jTVRG_i3dXDQ(3)0 zW?TWJ|Ha*_{;xh#;Gdya%J6wl=W2;;Ko{J1+6NRxBNEBMo_lAqK) z{@d;N0?I;c#)|CkjHM^iF&zwexqrg_QV0wIw>rT(fV-;p@v}I!qny=S30sSlw`1R9!S_y30x4OZh_I#DwF$XBA!~jy{3(k?3+mK6{>fBH zI3D?#mlu09l?e;}&$nCnoh%pm6y{_(KN#n|u^8w-)myJXrruj~V;m@UM5SM8q`smZ&X>qW&8L3lsKfY;mS^8~_Bq)XLu+oSzU!mSn5?e*Uk3x)Ke4iuqoyMG(<}OT) z@%i$=@Jer7B1M7JYN?gJ-fWy^@{Y!w*}z2 z+Tq#yv5FrS`VqQ{u-|1w?4A#{XFj`28K_8#y0V) zJmc8{XiYZi5-O38#)&(LdAY`qSLWr+d5SMPvE8+%L)@HzjSu?2T@#5lDKD^7b2evYOvO9wrB$2%Rs90{73C>eR*oV|8@D?+;P0$yt4yDR&dyT`VDe! z+FLGTtd39Sr>iYN4Uw5EDX=G<5Cg-L)dnmv6J7V=wo1L%Si+snXr7fa`X=63`o7NP zQ19i4O zGFwj7U&8s6a}G4Vjin2a=?=)8E_Whjgt16XM$E=r| z%g?hsYKS5A9v~dl?GQ62Gipg&M2@%|FI1zTIsMjF0e84bgh)1Xk7HO8%<6sbmnp8) z{%rUopf;SVFo? znI))TvB_ro{dvbVYL;TqaBTsz6e8sA;|I8ii%ShEMgNPfchvl2%D&h*Y`2xdHk zyXTi!(3S~1Ves{fQzH)m0QeC3hlx@Abd;rA@>f__0(AtKz_G|C8FVq6CVM)mhD=f8 zhU@fWr3Ju(n(rh`EWceSNSs6oRT`KMy;kbLLzC~%NXd67KnGr8vWr5ug#;0g4@Y0N@#s~K{c5`LWT=(Soz(=>E1+^-e zmVQy^8MBG@gPX08-QAkzOZi?!2tx&Ek)IxDTMa7mh_=D2mwPd-`{xwGY@h(v>cM}$URA{XUF`4^wzVcn_Ot2OR!U9k6 zsyzJJ(>T$UfK}%AYF(X&!05%mbplT^bzItH zDht}|O@jhiM0{2K8UrD0X1(v7p5d4jgDK*l2GHRBJrBByg`83$FN0N?CIZg`=MgfC z%-I+I;8%|m!oGUHPCuxcx5~#r`Y}A)727aRcGkk<$Ty}{)@cyoAI1v4vo zV3*$7eq}Kp(CmJ`vco=Ey%(Bq70a|IB`#JEPuL!%W&ooDCEbL0r*I}kTn3{`bxzOz z02MPijWqihb?_%~M9lz0==IFJ}5igzOzLx)@ zEe?ob%}S8aZYc0|rpO^a493A10JBd9{W{!PaAif1QE6DSoH<7e@?yI0Qx!e|T99r~ zxR!sP*xmHH;P%d9Pm7hc99?@{N*L;*LB>e#-fwBQGvC>Lm5l{36S3ubIP2j~&*?|? z)aH>DD6Y0Mg-uiDgT1er)T@<083qWaZ;q3hxef5k@2`c)%mCw)Iy07;)?Y%sa`#_8j}N{2xF@9CtTCOENmTG2 zYN)n~TstK+i0#)?dkBp!UW=`=n?)Cpi!h@#Xr%~#AzKtW^kD51^u;JcuK9SmeRxGk zy`FL~Y)|T%v+FckJzeDe!l*uU+#?o_i7M4N#ysH zZ0LV6>kIP|V3vjVrSOd(hiRZAU;aJv?5p`WrfuS{Ic{z#oUrCH(jiECf8BZjvL`h+ zouSl0x8W2wa-`evJyzTWr4T@S3DLaX5U15ivI?!5o`rEDHs@WBQY|nqZOngJ7oeVl z>+)Y8^Q28p&R=1_Uo0BhI56DNZV`E)p?Tc=T_>6^cR7lKZxQ^Df5939+G}TgcKnrt zKe1;CsCf$ij3P7jKZgx)pk9YU?YaI*#KA5m;-BB{SO0S?2N`;%HLgKwNMBnSR8cr{*5G7G2W@2&?`IZh{*QRyK$uo3doUOg%W-{lHtEW0N!px` z!A3_iSp=oI$r>tvX-EsjS;))(-xr&q^nZnEbvwot4-8VIuYv2HOb=qp6!5>zW-f4JO$x6)!8 z36+iKYaA~?*)O7Iz^Vl(-W?GYWvJ%`GTot#wL6?Xl=SIjxt$E)=1XM1+9+0{ls#3h z!RYGUPStF`UbrySPyJ{0ASFYWi>a3a0T%YX)FBWbYX<$_GyCr!WC`OsUNBvo6ro_J zM1FzXL#cJOi|M#;M(8cRTfHB{A|tu>6f#f#5q9B-rE1;P^dG-Ng?GO9>n*i)a+2r5 zo%4KX#JmW9XDt8h%Brc8+S)QMtt=J)onBw21chJE68_&m_9Lv+DvC%1_3upE=5fti zD6{_We=dT2WAj zU)vjpABwm``%A0FfKIoXc*FZxQ6Wc=;6)h!*HyZcYpqrQKwev1oOEpk7YnNZM1;Nx zfRaN9d?Fk_e~LL9+#l>TMMV70oD?A^U-nJ8NYf*gbav)uWmV3OrAUtP%7e!9KMx5# zayC7xuT*GAz~3Zoegnej{adr0eWoy6w)w}?7M0%$d}`$wJX&~jl`2^7?ss8P5j{5q z9Fl^!(y>ra#NV>uxsjbAg|?r+HjR95riZ?FLm{^;Av__UJ9RI3WKS|EF|6+O`rt$H zbblPmn92-g#@tUfg8UQwZ!TcC;6OKqmcPE8w-0%rcX~LT9Ac8c z*1nl4*q%8*M$kej?uh;I%<^mU%oe+l*cKkB*#v3XWnb0Wg6=hg)4}EmZfSLOXDrgZ>qaJjw#rzQs*d2?=G8mniruyS} zv2)YNGV}_EFxKc>(Vr|ypt=IN6y_kDx9WeKpJLmOPGX>c9v5^E z*DGjsn(adwa~3y!jtR@Psv_3BuD8427Aow&nz>@TpD%|nBr{O@R14Cjv7o&gT;M(3 z^N?2ftu`M{SEQcwVJ&ua%yC1-(qe%WFtEbU=;os-QYA3}yv`T?Y50wP&3AhEE1Y4@x00&j( zBI~!iY3=P93Azz+_LsRFY@G+JrpbONN*>PAYpai>%Zh?SA=+-plTazM;etl{ryjAv zT4hFC(;t?X`5_nAt%z1Lg@}VuPZ-+u7JV>vlb@%J{v=S#8(Z4{`GAqeWxF80&fj#o zlU9d-{Hdon`mM}&#Od|4eIA)QD+5iSSFgySpE;kZ{`C9-HA+n+gM^rqfN|(x9q&1}D$gXRrAg=y?YE4{Jn_ z2O-~TegRzpUS57mUS3HlQoRmOm7+SF@s*=$2q_j(q($^GmBjv+V(eeO>Fh@=kV-b> zChzeKUau!&-%dcOYjbhPhmXJ9v-ywOtCpzY;rUXQs7NDOeusT$qOZC9J|M|TF-=J3 z|M1iM5eLikB-M)V-PmO!?Uvs)9JFyVmt!*#wjAw>9ueCz2Utd*>9?n={!kzgiWAb+ ze0T(-T)R5fhBif4dH1;Ao$%?UT>Wlu&x9*%^ReH?+LqF5{vk7{0KK`=;cNi&wW0Z$7KpG za7jp!n&yyoOMwXo6@LhiqnNMI4C*Yvaz9%s>jJFT<3R06V&RyBVust)=jYv%Dq9KQ zfH$GRShCoFRF>|8@5ziweSgHr-&u8-_>fDYkpvWr}d;d`piN&D)n}>65V*I98qQ5I0{;-Q>1Dop>VZ zc0yR%VXF^Z7DdN~?3NJ+l%6$#NSJ5R>2*8Fhh$D<@u_{-0HpDFFweA?NHi0B=t?9) zLD3^F8`I_b5-Ek>DlFlhv;9Tr4v>4y8ZQUVQ%j3 zG{L!=A86q3O&!E`jX06%&5_KMHJv*iDR)|3Q6J34Qk$aMd*6{3FRCBo5!||;n+^Z ze~KDUs4T17UC(LSxnlz zD!9YK75OvDaj5hDHhk2?4v%dHOA`TpBw~fs;XoVMyz239A}_&prb8fhxWp1Au~YOz(dIc(j#y1zludsbpf*(I`@#b_UC6e7dq} zk#N7D2R&YAhu*ZGcd4N!LswBjq!ZX-O);R$6*ZQjFab$b5=Kx;S_p+M@-pGbg7_yD zXt{ngRjIAkPfKI+cP3!>vCBq|3zHBx#t(8=I@x}IBHe7K%?2eFA2Uf%V^E=1ye?Y3 zoA|Voq>2WPqq(Y}$kb}1k?jGfx>d9Q7(K^d@3-o{I~=bV&7Z+!%Z*bGa&%`rZf99F zH8Gtt)WXAq$}cfrL>}J7V`Sa+EUmab96CWok9o#Ha7XRbT(^cok{FA_mWIA4C$bQ8u<4#+`qchj#9HsVO)ishkx_BxGLc^hN1eA|XG z2m@k-W2o2%9&K*NDLO;bnR@#ADs?7Ov!u{cdVge#?iaD|pP`|Hwog5UX|WQk(iApl zlh7h%MwqYOLM-$SP%dT&^cBQ@tJZ;5CErZe1Kze^F|;}SUo@Ple|?|czGy# z)NS-W)ltY4#F(HQ{<1oSi4uMvBBdIpJp6saORL*osDFUgWWHQunAx08<>-vA1nSkp z5bz*6UZ`Xvu_Y<~mR=~Cb)vBM==Jo$oQ-Vx#FaCTD>sbmL!I4zFJmq*O`gt#~a zU0vI`YBfB6n4Slx-TR9UXKlqtr<+5zc?74eXZl&qdr}TjUp3S@7pOM?C;vB8)T`Hi z6i&R{P<~s6_^*8ngxW~9`_M$x0TX}eE?Kgv8+OdjLi-#mpx|!BgZn@CyN;DSzUDx! zqP*^P{$`!0-U>IWo`i8Zbdf?4CgJ}8JMhiP)p>AoXRsorjjceshsuxtmY`NFwmMs- zT+d4(gPo)bY!%JdV3DseiW?Sg{>ERxlwX<`U0Y{0q83K3;vXL(3d-c5l}ln~3S=RL zqH{q_(QLgD9kUhipd`jn`tIk5g(_XsyGrm>y}1iUoRf#n`X@a4h@!VjQD|2-w-!(m zLpDI0>l>xlb{%$sI5XntAmC?q`}V67Jp*`%5nP?X@-zN=Xt8 zQWMkB)X+gwi{Uh}@Xa0xVQ9pXu-fdR(dqvFEd2H?-z3asOpSMDAX_}IT0ZAetuR*CH9W2HLqk3wq23Lvz6d!yi_f&wZ%_0c>G;P23oP>O;F z$rR(<_;{7Du&5vKGP??c%>=PHL1XD0eX{-Ku{A90a_M94aa7O@Mi46>5IOzb?6Mvw zjnm=F{zOhluhcn!q7h0Mz(9>IHsqOw2V>XBh=?iIqrQ_!yfOZAu`JnTTnUaM{w;p< zqwQIIE_9b3kUM*v6>D5GVc{;-iJK!{izQV&5*+HGWLv67{6IoX;>xS#m5olCo&@Qc zl5aJy8Hi}H%H~mcbtn5NXE|5bziTz72GX|*JxZ1~tx7xGCzO({GlBhEon9KakFS}o)n8~e!Y{Qvh8Mu`W z^|h4AA!DY5AVIK!KwyUIzp7*plY}VfNP{8o9D?jNU#Q`Vlt-i3TYL{Sw3qdmAAX*% z_+KoYQ(zul+qT=rwr$%s+SqKI#={&Ht|9M`E2w6e24yhnOZ1tUnRw-@Wk!Gj!GAm7$_ztr20M)Le!I(Q z@lM~0_A*d`nES7~;NeK!yv#e&<#`}tqKrq6Ha^r9gRiFbU*eXLB;0~*`HvloNlz7Q zgqsg^-cbSkTLBizbxW9aniP_#h1W;DZuBd)`7L)7V#)r0VQS2Q*8exTsNtxDq=Dqo z2JJ$Nj$0aV6`8{sZ{`;Z8J@EdY5yqqyf*3Q9VwVRm28rV= zIT#WN_IcyGhZ>&Ze`GVbyj326Cx-O>j`HAx<+dG;?Pv-q0v{Xs_#fNg0cZfPAJ&a^m$llMpR56Tn# zXu6>L**7*e7W$rBp0aP9lm-M4(8IID=zx|PkN^Q>ga%po4O^TAX7VVEcuJHE@VXON zj3rgd|Ciz_$VbA6PQkT!-dn1*0c9s(OQSHSq`|!7PpzTUX|YxF({f9%5dYZk!}^-# z&Mg&-e{vgD3R<=t`JTWt%BtU(QL@a^0-iDceKt)1XqIsOU*@bJ6A9Co&Stg)0HL%d zp~>mM#X4YTo_|Rb>p0+Y*;XF3&v)=%xovd$@Zl2>T!xD@uh-o(>34aLWbrh-yz674 z2Yq~eTwPx)ktST_?DaV+lLH%|Mv)cp*^#lbq5`%UO3hmm9d7_?4BJIauiaQ$KAsbb zjus4L5ktTUI4?FJi13yv+P(;U4yoVz@3Dv6j{i|$ybFsg|8=pN)0Wc?`-u-^g~gbm zzV%Z(b~$`*w&>J~JxO%;$mr;WS!xd+?0gj+OgNkw6f&6iKY^`B!27t*j2^hO+Y6i( zdl|u^&QguORBs?ef}oG5%1L6vPGG#9#;=LAJWwTYRgFNPLTR+#>Tu$eNGaDy>@5LU zIpk{B|C%f>uFkt14)Fn_)2M!%;f)TA#$bkW`A&l>z8n%JRk|pRrGW)No3(x>Ap0V>y~i+IP$9v+@_<;$BtDPNoMy>G6E%^xN4oXyt|gM{!)AFe9_f*ViZ zud7%)=enJaYz8;V?CflD)1xts`g1y2mR_s<*W1JCCA38=cA76XzW*R@x;xz`Z6BuX z>evvlv(~Tg^HakZrhi1XAWMpi8=TCSo!Ar|tZX%zfG|TVie{z@zZYCR9`;}YIixDd z*9oLgiV$z;lf&;Ttdk7D+T zg(0SQ;pjj=|9ZMRo-3Ci0}g?srJ7vaFH*q!#5uqeb(;6b5zLTx?NOiBq!&aZo9?!d zVC7!b?iERa>Mh|X9r^;bLWGp?=fNf<%^hr$k%E&ZlVHcWvHSqWz{hoWD8|f^WIm8N zx!!UAM>GMSdZ$mZ@$Y$>nGWA8B~vSm?9SdGC^{*{+qc=ktA(8wkZ({)Nr|WLay{@; zn)u$Kh$x})=BvVP52woFSP>hsjXDx{;7OPqIy}g0%m{)}l6vqPic+c4S z;v%HkbdH#bDmo-0ZaBb_GVgQ#Rz!e&BY55BED#a3c0u@9cv~J~>MJ{%0 z>`T?@L%}u`&hBsxFZwMLSkZ|=Nf161FkZJ)6a`9|cacf5F%dGxZ8eyV@d9Xdw@5M1sm_iAIM7qWG^!5Q!Bn=@Umf6V+3lmj$5yuRO&2u_!8k71jMC%x`wEm;R6XD@ z7^7YAI1R3aWRyC@VJMK>VqIUquXNkx;tm8dd2XI8ql~N555LG|Ug?3lY7^>h)>~(j z*gPhtQubxUd=F&cf%NTWdF@LG>+&COPMxGlOE-|RU?zcMdxRB3AM!)}SGkyd(&Or8 z_0se;M2~-3WG(aOO2F!`Lg990E{UhL?$@>5gFn3-M! zX)#p-S=reJWcmNHru@zcqM4G3e%-fDhtCk}{&keYcz38n@#>HF+T8&MXqe??&Cxnk zhmSK0adCmR&V>$q=Mi(-ASZd3qnWQu)gHa-dw*q8&ddd0usq6b_P;J+yq$(O#USJd zz1R9a5f4br9kWk~49$A4e15<6o+T-$L9`ys~33zK`D{v23A4#R6%fiQrx;2^s zb}Ma zdE0D}1js%ma|M0oJ%QEq^xt0Q1lTG3L@`&KAzW}AX-Elte>=0OtnH`6gt8klR$K^! z7PZ174+agHi3x>BMR;OAd-#cmO7)DcK-^OKMGfM`ZEwU0O0B&lCqX;8I`^~?bw?R2 z?OqUb#Gtu1c@Xb(_GAjCKdY=0QGu^!y z0oH{p6sPZ>mn$z4#uZi|o~&oju!s_3`6$u7Jy&PFp$85glKxOR%bx}0zUAK!aUw*X zj6!lJr%^v|V(!XRN-i>ktS4G?XR4&Q2Er3D(9mL*TQxCGc+9W1N>GD>?e-7fpP^_Y zsDU7qUZ(MEZE^uaC=Smp7n>a2?-x~U?F7DbD08;F_4Ur+f*Y_u*I$jI*#xC=gXIs3 z4~&Rj4|R_+#I-Z>_WHLDBd0;4STN!^{?WY_<)(6_rDxTXKdop1;=khd#NwGiW{ltJ1U@kbBD(Dk zc-`+!gdD3rp+-$0DNw*}?Okrw)gw|uhRB3ssPj%$JJ?0Fzbd7@%QsyUheV2OxUFqS z6M3XUH&Fk29@RPv^K#iwTFYAuln$#yXr;K(&sszioC`lDCsdH|Da zRkvXzAFaPuR-VmpMO2tlJ|}Q;t$8c+QgSPd$Zy~;!YAM@pj6zX0Q3#C=6yf3pIXiy zb)w&&sm@kgkbbtiaqYLl=~SrQg(uA_57c+a8lMtF*d6~XTHpoNmiM;GkpbLtdWVI~ zcd2X&T-kRHW4-CO^$Uv*0YqEvzuq1L;CjPqc7DqdBXHMkp{F8s)XKGA7_)g|;;%F@ z{&~EQ@rYA!=c$g8{)qPY$sI}5O%Mx+#{)=m;d_&CZjounQu5=QgN>|#DD3Ix))4#V z+Q6IZ7pnKSeuHt(Rq#iz=Lw~(&Vw*l0ndE%E_!GL7zuMt@GR9! ziDAq}PpP!Q?@={fsW!I#E348K9S0Hozucc2oFfvQk;Qn0oeK(8O4Um&B0_;3WLt31;L&$TZ&!JG5WQaqGEi;7|AjCbXuK~&NC0Y=AJwH^lx})kzv&~w$Z#_ z8(TrEy&nlVki#V&1C5iTS2>&<8JbL}e0g>>m9h@!8j8k^!A`};ewsyg_G#dWy?8-_ zQ@0H)2OEo-CBP@!-6w#G! ztQW&SB;};UyoQN3&$kC}wgUxPQ_$vXEVTe$P~Uz{{4CU_u+!@d#Lm1sdbjoK__SqO zmUuN-Jc9^taF7h5oQxMR=5;q24U5g@&HvH%yY|P8qkQ6xxk#F%QvRqYcX~+D4~3xG zb#){Dto4EA<&urt8EhDYxPkUuIVI7n5(mHS0|ucoi2X1ZhYq?ga`NXY#+zZ;>)h)# zXAkqE*<&_B>goQ!j;*HwKk=pguYxT%a!4&1r&DgeGs8M4Lfr?J9%#vqY#2ocPD~G#( z1tN@USWQL)c0z8pmJ`>J*`EeL6 z*6!LvH-NkG*F~%@&_k(2#A&`#5x%|oA|N?CyDl!bi3}3{8Qvy?L%e}Ad$v*!B8$fr z{DTCA6uU|l$HFb&*jTk&j7vj`28p2cwY~1jR6EfXka)#aoBu24z+EZ_zmz#dp?R)Y zv^)Yv3j(VkUA^{qNjT}Ex-Xiq??!ry zGu)5G&oAB6dIh4=s?gLr-x5_ZX(G$B_jei=ShC^xYu5X=@)DIw`VW^~jGB`SGeCcRbkrUFSU)QU{MN>n>_Imufk6`O9UG`&$Q-b`1ZC zwN;4U)X-MAStT~A!CDbFJs(zqBvnk}J?Ju8RMjLuoED7oJXTydNu-HWkkLA#!Np!= zg|*23zE5i%u{EXvi(jeH{kyx#SYZD1?bcp|F?7dy(0I!?r7ls36+eFOpl{m-WW>*-$j@3uV)zvjiHAf$#5MVIKz~AiCl|n|rq)y|9OPVGQYJcns zv#_u@)EaG@>X?c$O!dc`hh(idk&rOrCa{niZRidbJ6>q$h=)LJUxNJplc!$IN)hdX z2*S3{1{VFr7i!|G{=h_Bmn*o?^VwMD@U8mD1u63XT;p@^OCRrR~4OC&-D(wrL$HL3FP+ zC#v~m4M?EuO677Bj_$XoTYE7@G@2pDCSUTIpyP&@2{f?I);-`6coBz@60tJXE!o}j zwswWt{sSJXf=44nZ-M&8tT4?~4i|bJ{&J{jQqUwhM$oS`Ns%<5@RBSA7-?Pc?DR+- zPUk9Mj1NX(sh#plb(#5XSLw7Ey**to*BSr6SEC69d7t9P~-2z}W;3MgpZ zCkp*%8W}}Uo7!FE@cxe{i{AqqWzm-UXQTg#mz|9%7@m&V-FrOg8JRb=(b;%SSAQJH zC$x1WoX>RY4~x1ZVI$4;b=}Ppq$_%rJ{8%5qsh+&S+q#oX5^T_4OiK1@Vx%II_E-M$jnc!J zKt9kulg_Sahn>OfAKzBrBY)Ja0~ea_|5&N%|8Xm)bYc@&O$izQXzwQ*>+=C6Qhfi| zi_Nz7$U$B$UoM5t+W7l*(@egn$52Er#BYyf-{~JejZ$%H??sTAQho22$``YWa2BOo z_}xMjg`o%^mJtV>>D`^e(5p{SEsgo=y-u*;;NEtI1`#5Q!dfQ&FA?(5Ivsv0b4G;3kvqXYVK|oW z^nuKJ;6L;3IMbd(@yhob#9sf!rmfGbmAinE?)bOJ@EExpYa~tm3aUA6+p^Ij!jVw| zpRhO&g&aIrs__^2u)KuSU=g4_`PX;|;YbI8SUMbsl zQyY2H3yS)b=rY=mXG@$9a`gX3x+D7S-JXvdaWE1~DxohVfzON+&~8Lt8X@});j$yaOG_+MZtIZi>aC}Lum zyF$N}5E2QUv-;VnS}PN7SIjoG1JJDR%rNJsCjN54bGvo^T(M`!(Uon;GFZ4(^YL@b z#hplS(-~R*A{_;rk8ZM;drWrSCmY_%i}0We7E!*7FN2Mo>DqvUpIob#=AsP*)-K# zRwZwXA4zNYxcO;m6Q$Mjj+v69D$;iDpun<)(JfX1Y^;8hD3H9g-{ets%7*XJoG8TS3oAnX3!^cJM+E(jecDej{2Kh-o? zF0wf_7N4@l>F(!hzsqD#+_v4mW91@;dg=czJH^B}iW}@)8?mFsNIQ#9#ycc2(JxJ% ztPH`snr&f2L7_JqPjF}`#wdf)d#jaBqSZ&grSgrwDS}N?=f@R5+CKgIdOYhOyQ;v% z^YrGB7hXs`9!~)M8;IYVv?f$K7^}vJ#j957ca&6bht@APnP-h;iNfB0?(v37 z@q^gw;S8`nKG^S1#m%Bh^yyZH^By|VxZZ~m8?;&|hd@F?np^eqnCA}`x`-4$=YJ^8 z=*MKKUk&&O{60DmwVOv-ky<0`(8$&dt1W{-M0U^n6S}y8i3BP=uVbfS!h(vM<^95{ zj{6|5Xsw#CFW;z; zKLY!GA(h*g0Xex`HRZOhw{o1(eU~wsr~Vd%e>tZ#zsbpL>Q;Q^=v7OxpEab z{Eli>CKp7*BH?hkAC|#&Qi5i!uh0ol|0*@m&+2x^fozw>7H|cxH?!}r*Fw7iS6oNLgGHNwm3i3=5$!=_v!1x z^871mXv>rdUM@cY5D5f!W@6X6{v2?`Mu?r2V&2-zD1z>E0c(0i#3V67T9Tcz|B(wJ zg*7Pw=@Bo(X;BUyrxpA>F8KJ#ft2cMi+1fscRTV~ z-ypfg!DPai2BN+yRiAf2v$s6&CYAq|An0bBJheEumDy6rPZv*PC!&T~tfHC?`U}&` z)e&DNENGl7{9UNcGi1!m6lA;1(h;{CJ43n{*SY zCOPEo5wpK#)Pe4{)ttq4cvgJR{nwkNBbG2Ar~+=i;n#k#A(K-rzDN`&be=dmjRouk*L=t6iX*Yg_Gw`Yn(TxY)^e|N@(=>SuSQ|2p2 zr;O)qU-+%vXPjvKV=e5V%90qYq}3gs`)H)g$9vZzBoG2F-+VT|m?J|2W>E`J>8j@v zQ9!rlz~zcX@<#I5o>FlJ`FTvIoqL#@m%fZ+!tjn)QrT^Gc$jUw$>a56iZ&p7&tIpf zCi@0_gGlmUFjs~{*Z;3`^IzMThKZ=le0$vWr5N8kg2|Q;{Px>UNz;-= z0eCe|hb{hp?Y)x%Y?cdUQTR9;^Xq|}53%+RE}|*;&x+l81l%iY!U%6hHh>h9wr^ua z{cQu8MuQX?x<$5ardWb3_$i9P*e;bK!hE$)^33e-UHVL=ix+#D3dBRc=Pp{6V!5?eVQM`#RV!ByhtIDhMm2no!OtI; z)<{J9LM9VANOsLuU!kbD(3#XVfBwmOzO8tdl$6Yqj;J5Wq@z<TlzcH>N#QeDdoS=>^o^*$0fZ*F2$b+X4bUFC=+Ul3nw^emomd zFDl@LxfM@|?K29fR%n%8%Dt(Uz{9Fn1R<$2hCR0Fq^-4sRTJ^~pWWC?g@yK9o?x)Q zKgl!_vM4u^>Irybce|SVqsC}Q`mfR(O~53G_=aN{>orN~JHMO$_0}kHhDxDh}>BZ2I_f%bK&rdL3RvxCJ5^;hR3r?$@>&*qaMdVc?aLz2$xNw3&bnT2U1 z!ADS%AGhFC8sXpxr)UdK)ZkI%A2hM{yE)(G$6;Cby$PC~8Nx0Nfm!Dn=0*Y@?RU;o z$#*0j%8&5qXSlwp6^Isj0%Bp3;B_X;T=$TmPw-YM&LG|xJC|np6y>ihh@A}89}XEg3`s~OQ3(o7Zk@u6p~#)_wjy! z_LTD|(J&oaaM{;HDvUa#3BFn@fHiCPERR81mSaf+i4c)pz%bTy98O$gRo;!S(`rP9 z(CTTxltnSUFQAk3RFCH1=>gKC(EHJ1U4qM{@p(m2O|TkAkV!8|m*t)DeK+|7Sg#5N3*ipj4K8dCleraR zEQBa?Y zcdR-dX{^KrvDsTclb&I+Xg+p|Jb6#!c)L8oZldN9;-FRzypA?i)H)4`s~)f8=@YaO zn+irgrds#xi@nyFo|t2ZW@bG5%u;#_TI#K$28sI;#fsye5e3G+OYXiU;}v7zBE$>% z`R*6aps>TWh4u35nhUkoDj$fx%27qYNpbHPeb_c%9xV1y1q>UlP=hyN&ztMfACxTU zy?vq5xyC6xk%UgS&Rt}Ge>NZw*va5uyC_OSK5ASSEWb~u1 zrXI;RHtB@biu)#YXA&D6>~3sTPOn=dIPOcRUd(;1RbVPKG5#(gCF8p)uazdBXCdg@ z5w32m*`Z{9!)=|gA71R>P*3zcA(x(es>VYRhZ)2ax}=9(tfqPhx>Bsf|6utbTqgL8 zc_tF=sG&3KeO&jzs2;(5nX^jm?;VA(i^Q&Ta}gEI!hls}iun63B;xK*G-bC!Eh-(I z0HyN})$nj&Mum#I#7zUKm;&CIe(?{_4*C0--mdf?5w4oP&l8E(!x5MovM^-VH66JC z2iJbfg)z-Po&@V%-gsDUw!$l1ynjTaGRCeHgE?Mzq?T9swr;wzYgu6W(yo}*nt%0o zBNrG%RZ2MK9qCl!|A_H6 z9WS_mBEk{lF)dqp729J}!#r%MPDR^Qm+a4|kd<)$v>{oqHxX_&l1N}|$Aew6f(AvI zIh^jHaG+W(+xTT|G)hiY5wc>@{yI~zU`%>5joQpBx{aUeCx{92g`j$L-r}#if4P@> zWtkhGjwIG?P_1_IY{Vo1%F@-|uS3+D1&I8Qm#oc*+BxR5J9}+bN3U&JP_Jm3(Ud8_ z4EFa-;_z}d&DZzwRPrT_;qiRd6}cL)*_jmj6VHN9El#m7QZo9kfw3$h;trV9tG=6f zAxNKa!|@0-4c_$Q#4Dhuccmeiv)rjHewId>UvU$q+juKa@D}-8;bi$v!&7rQ$puyHfIK86lIs)Q1}wUm=x5dn zpKH&d&|7X!%UUA8X*$}Zof;&souvo6t(F$;(aQ(Qz-m2FReR)o(SC{=i;^sRU!fuk7PG0soG#)^vefQ_`b3Lw4D$hY4ZqFyYeSbbxLzySF|v0xSm zQIW%J7fJ8KC|Xmv5#o;&&+WAxU7zaPOQ#Zk`aa@Na@9mK=Mpy|yh0U^XMjoIv5O?U z$n8JLDYnWRvlDQBLlAmdZ6)IHM1=VDCSa77&ulkHJMy!OHkRQtbAx6-YpGwo+Gh4e zPPM{9nP_Uv-E=|llCFkc7Y_>!0t$-@y?R0VClVus)x1QINdN53X|CbnUilP5)Ae<@ zR;&9Mn!Wr!OI2Ky6feDY2Tu1#+h)?ASz!difmuEtRC7n*&Fqb(GQ|NF8l(V4H`Zpv z8$yU>J3jc94JSc#%c7g~Zp9r&+;S+4h35DyYb$DZTOszT)XM-=a zjdnmntn$^50TgRW=k|rsk+g%m=G#tM1Ja%w+Q$&Yv`(@3Xm7$W`LkD`Aem85iBeQZDDf>!`8>| zAklcZ$D)$78FM|t$_MI2?h??gcI&4`k>^4oLnEq;F;Di|s0)RDix z&AraEG%-P8?dH+vB}8Ivr<8bOm`rFupANqv8Fsap5HTb}Gi?6Bsg5FE{}{*Kt5F6^ zhs6}d?^hW-z8*RXt>U`zG1}hKzz;3>oQx%&*>|n_F+!0bzz^MvwbXL#{~a`JAwP;yXmSBYJkg z+k)jz@gW9&ij!;d=`qRD`&iCTO|~1W1tb=`BFk2veO68dTrdq9xbcCmER=w4=zAD< z^K?B-0ig_oGdAZ3ayp2{-H1P(xYGQ^ zGA0XDn z43?|4ZIP!a}_dk z$`FRQPxx18KHtpX=r$+Aqh=NR@v~SNFC+ukrId1ts@>Aj($8LD6t5k-sO7c}Goph# z^B8a*+m90$az9RBkptm69d@mESfLs+iaYY$W@$_ZM#c8Qwb+Py2YKJyn?&n|a7c6Z ztkb40$2s_==541P(~i|?BhAy}X`EuIs~B*?$J}NvmN73VH0KD1dbcHwWc<`SHP(FL&o%sRnKi!|jUNSeBx?o2#%i$> zo!Tk7`uG=PKbawSUtwZ(fb}e(*r8|aoL+~_*}SOyi)i9#2H7_UE552!R{=^2WgJQ= z&qw1fi;c+eV^)*Bl`6JcGx`mKcC;-;WQKvs{bH?1lG5hAAA4a*g+Pyec|6C}XvD4C zxNk4SKeKcRoteK3@xE5v@bv}2u!{<5X2Ww_ckV;g$ir@nj~Xhy)29w+qE`~gozLiR zwGQ^pjM{S2T1XDwDy3FVM%s$lGa7p;n9spWa<}2|#}9Q1zE9SETmdu{6H>10YQ&PS zktY!m(LLsaslt^_sHs+~eO^XsT6KUJuUnGkk`{k(yL@X?OfodOk|`-i?2bv)G3uaLsnLU~=s*SQ zoS-N&$JDCmku6~$b0X8J`%S&l*aj;h2TK*`W-m{*!w5Tw*DO5yH>UUb-%ozlNaU!% zDEx|#KTOf@Tc{?F^>MNgPI=Pr>ZFc+rV+x<;@GVFqj9V6f+Bet2!*LWrd@C0*M^HTCQsLL1=_rJNU*hU)P@ zC>Eg97WVGz)i1Jg^OnoiNhi=GG7tMhu5y$#=PwkmdzS{%c-T~(FExPm)e{dR> z$x@!_Z!@sioJHFRUSU}WlF&@7*exLBWv8*IEj-BAudeKORS|D0PbHWv3=NG@Pf@x? z+_m3}bfD+7;qqXb|FWFT-Yl`jjd>`Xru+fVs<3dnKF0Yqn!uR8g!*99(6Vf%T6=A$ z+^Ru9xS7OLbUD6SrWTQn0CvFB7N+C;j=`P zo@?tm!|Wp=`(@lWFX8z6raoOO&s0gC~Yu#NQA_CoDwNeno|@2-5`% zy&OziE6kv`ajd{B^*|rw20#z~k0YZ|T-%NWNMbbxFcu0WsNMVC{Ov#6Ws;!TZnx-D zYB6HI#q`C9Qj#h*V~M1p$Ci$|pa;TFdL!TIQ&abYU0}rAMY-dr(3N46yFE-h29KGo z=)q90#Co(LJdO%_Db?q^{?ha1j4I#T;w9wzF;@9NH{41F8Sz#$v&-XG53{`W9Xxm2}dg^$BAdNPSMd zmbY5xNn7Iy>wiRdYC%DM*o{C$^>vLbJTZ}i8_Sjb_*N6VBZU9A7$_;T^CXD*Qe)t+(9$ zs@v^?S1S{tsKoP~lV&)AmXmMrjnL})9k6Nfzh6VFVAcY3T(!wpfW)5LaSs{*)MEY+ z@8Yq;N;3XRF#oW6Ug77Z!E&uLXo0w}`uupe{DXTi_di@jB412$&U~cw;C|aQG+Nz4 z02~S_2Miyl|CRnnveAq`eL8>q6k@A0n=p*N7nBD2R662er#G_Qtir=o-NeCh&gEOy zo}qhjy;jeAnD|yjjpy#G!rxMIK)zoWJiQ5c4J20;_w2ecqI;fQ4i63vN=(w3V}@1! z!a*GS8(I~4MmF^kUdhx3c+=biypu3+a|;i+a@yooVbM1$doqMh)Gd;U>RWI_Kx9_> z_s{~sTj2Vzf|A}NqH+f={eGU`j-v*5`Sy;eH)lE!+-SSv8v5t1eaV zCVIGh=sR0rn`QT7kK}lRH-tG`PB)mCHm+Cuy*5Y@>6kPfTosY^5}2lgJD48YX*0G# ztUSf<;k327nSN3M_>RbM+HFSPpDuyORwovyb7jV9lOLm>+xN{xQOsR=Cr_89>OAjH z{(dG?PX1nmASnrMb3M<%t&N#(kW2(nBq=5)CXUPVx!507Vv-!w1p8nSAY(E?zC%K1 zXP=2dt()=uzcorop!*_rCV!h(`LB5>(~OWX1o|j8nW>c_W~F2=-D+@*5^x1>iKN&9 zwt{`%cH>E`es#RN8hc;K%Os7QGtzQn+7cemmdnE|-wwyg30I^nBv5pM{WHB1E?A|Q zE!2{8wqwfYjwE1Gf#&0L&+dl(>@neu5(E!@pG}zQ_&cMu-s#1KPrx&F`;+GlXZv&aobMH470u^u9nc=u2 zd1JrfVxamrthpIn26opOU*KyU2->fV?#DfIH(9WKvvc1-`Q2EljopR4Xe~hAlwNwB z6xiP_8D{v@I%vM|fG~k4gzSqIXS5obSUZ-Dr=y|J6P#z4ZXMUw?}q@x*HyWOlIDnluX;Gf&0LomrB`0 z(mil`uB9sO+CVqXQ0pIfV35GKRjUedMU)JB-D4@u7^a})M3jot|4lTrne_Q0Q^Ok8+lQDHXpaaQXbpMTES*3gI))Afn<#Gw#35&jdv$| zSt#JuUeP6CySjg(&@)1Gb3#6yG2?EmczVa^u9e-$yj>TuvQ?UP`rs}q^1($Q>v zfHry;VDY<^!1$rft$~=u?}~ruElwjDymgdpbv2SA;=3unPfs;KRX@xwD*zwEK(pP& z?eM{yFTvhNNij~tm6G-7F(+Msgcc0goD2e#;=CCw#AL-d;JcxAO6+Hbh~HmE0*l31 z$$Pz2sf7kCXsP~CgxOXHV|JTWRR&rb>SSgWUh<{ z`1&a0hlWG0w+770l^ZRLFeQxi3Mq&8k7MmL7^QE!>?CF2>q8w15moCwN(9>xC#TX- zglt_>7}4?4e^Z)eu_X1KTE5)hjjP`u&y&V<>mh?HN1sH}SFqzq_Y2L^O5jMuJCjKX ztdz*lNcuV2EQ=Pf{?4zRFRpJK!W!c_A=<$FnqtxM4_JS|KIEG>}X#WA&pda`Ea@DH}K!%Ch z+g=un@KZ(~H4s$!W0xYDj_kO#4S^WO68o3+1xls-Y|u+$Xj`eUQB*F{$QVjIM8ep+;%}?#o<9wTX+&I1Z!qV*gW(e6vI@GTa#qw*M+~G8 z#7WibpVHqx!*vUBfpb%keoWdynb$bAhNIN@%g=E9Mx88RMA~%0M_e4IW+H;N=Nl|p3(rhaMvZoYsJ*kQG5m3Lm2I%T7fCMZr8lgvhPVEZ- z8@D(4ms(fr^8MDCSvQeP-f0Uv_GSW<0QVYFZyGNe*WIl}U5~*;OZ&s;!1Z8eT8d#} z_-#FQ%}MA@7Mtfz~Geq&!Pb}&aqWXWCzHAc$&5C6#Xv`Mp3AN!kv zP+2Da1pzaxygIip8DSM>@Rq+DzAkrSqV_9#t!>KRL?&Vzn{&A+2U`^v`f3G{`EdeBkADa(Edm!5tS$IjMS(cS5XDN1lAeB2yAw8Z02@-Gkm?ek;a|pgdzhL?|MtT zf2NbPqzRHsjXM~-B6IQ+fEh)TJU^18{LjFeu8gnlg(e_=Qm#noUVNU00$n|%$_=Nn zQbO+Jg6R)^{c%g~<1PjWVx4>s zkV6iQyqCz=(W_&q-j_XKDgT(i!EZwdu>q#H5_$XtC?t|91wIs1)OYT=U~rVbsr4g4YTrN+zHP2bNY~ zfZa|E!O21wn(1gdAD1pZ!GKwkW1)X`z38`Cg4HxDrJ2qRTsYZU6IerW)ajaW z?G9|F;Sm`bei$b5Q#CZ}FjM}0cbvuIKEOZm;)4)>!6O$x$!vu3(-K`potjdUur-Cbg~a~ z7(bCjlQ=dG8sit?KmGFY>i~2v@#z~E)K-3=&qz;3)$iu$sC+AbUDLLX-t_r)i}dkw z%Ct{jtM}`a9D?Gu?$Q{rSDz_Up=v^kp)7L$2~I*J)eKvYndV3gt6znnQ59&waZkM* zCCT|#+yxh^?xwsz0UV?UvB>u9K-(zDMkvvLw|?E4Za|QC7IR6 z*wkaqb{=#O9s2f`rOJZ^Ubh~l2$u`B&g6&tpfoLX)n2ce#6p1- z(t=&Yqur04i-X|VZoA$T8?&#ejo0`k+!2l{PA3uFl7Fmc>9L=8&G%)U?2%y+9tcd} zqS`M1tb8{EF(V$=Oi z#{pI=5)A9Hd1c*iH{({X-$W8`Iv4}v=Y9kaVl$f{=(D}G|`g=F!?`sa-tm>=)46GJY zC*ItXW?)fFi;R;A#lbQf$~BC+%bcT&yFQU0Mj2u#YNl4y-r$Q70_R9B7vHZBcGNX_ zD8LiD*is}d5`a=5fIm}vd8D`1PAWwDY`Y&G4tum}gt>OS)UFYbmkSLFNR}jT?~kAb z=&m0cHCksJ zw)&2#ek9YHu8u|(=y&`6xKadHg72_J6ohz@BJv3a3XK(56zD$iRgJ-h5A-g^O@ z@M)I#|CLGJ(gTlqW)?Hek6$vUO1yPdhb%>l!J9qw_f46xJzPoV7TL)3ucz$9MG&lv zIIonIGvl_w`mnJ9X0Uz)F+Phg<&T5!H3K^N^E!=C4oV_V_buHse>~XmL2X}41Ui*E zbTORImoZZ6mPX`jc*q+8j=(W2>~j0?~{= zj_Ft|0?K82EbM-QCBqia*5MfIUmgF2y5Q`A`4Q5veJ<^_IYXg{>uL#zm` z=&tHYy-r*r@2ddVT)jFYh)u6c0qe~U9FXrT!=~S-kN3?e)kz427zK^@a-iqcmWXT? zx6*sgcQL;?Ty!sfs8z7$_#7cWy%^2E-p`Kv^QDe&l-)O@>kEz( zrue(3%eK=sv+vvgqvyF0;cae`-Y z{pNYAzF)9avorV1>C=7nH65(XV1+A*m=A`mN^a_xbg`N>^Si6}xPwOC)yd1ds4*#HM;Tey|%k(Td_WMoiy~a=v8d z``AE7fvV5*2&fTVg14pd2zH+p-NAov!`;~bHWdl`-~&x}m3|YFk&zKTw@rep zA%8wv2dnc3^01gpP%~NsTt|l@VWKc4Q@Eol#xF)B6$|M6 z8`OJ2)CrFR$U8Rt321QK9ZKx(X|}{6|50Q_|HA!#AjX`FJgyf;*>>PlmYP`_pvMec zmcTY)D;;9v#MCpa0^2)<;$ud?E{E-9#bP2xSqN9G?MJF<*#z-yo!z0qN52xi%*om; z|Hi9Y-0yB+Wreb2)C?)|fW{i)7|&qA<~(2u0%|3x7&+m8a#{xGP#~GAaX5cI`Vlg_ z{@}Jn97A@`#d{_U%k+m#ec8Ge8yI(j1{`1Iy-}Og_>>i$A3x*tBM;E^=0ODImYfbWRa)4y20E!BdAGdek! z0d|Fn0Jj$5cwF93S1_+MkHGaz35qaTM9&e@QXi5fR+%O(7^x(&r=7AA>R{Bd2Lx{h{1`5~u7 zkTJUt-UG4%u1%iPbz^`>a8j_k?ylC%IPRJ`GB=mg)UwU(3|0N@rIrFv#J%0DQgMmd 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