From d4ac6c404d1194b2cbd64ff9c34a5ea10bc5c542 Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:35:40 -0700 Subject: [PATCH 01/18] [C#] feat: O1 model support (#2111) ## Linked issues closes: #2103 ## Details Added support for `o1-preview` and `o1-mini` models. * Bumped `OpenAI` and `Azure.AI.OpenAI` to `2.1.0-beta.1`. * Tested o1 support with light bot sample with monologue augmentation. * Updated `teamsChefBot-streaming` to use deployed `Microsoft.Teams.AI` nuget package. * Fixed `LayoutSection` bug with incorrect ordering of sections. ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../AITests/AssistantMessageTests.cs | 2 +- .../AITests/ChatMessageTests.cs | 12 +-- .../Models/ChatCompletionToolCallTests.cs | 2 +- .../Models/ChatMessageExtensionsTests.cs | 10 +-- .../AITests/Models/OpenAIModelTests.cs | 8 +- .../AITests/OpenAIEmbeddingsTests.cs | 4 +- .../Microsoft.Teams.AI.Tests.csproj | 4 +- .../TestUtils/OpenAIModelFactory.cs | 74 ++++------------ .../TestUtils/TestAssistantsOpenAIClient.cs | 14 +-- .../Microsoft.TeamsAI/AI/Clients/LLMClient.cs | 2 +- .../AI/Embeddings/OpenAIEmbeddings.cs | 8 +- .../AI/Models/AssistantsMessage.cs | 11 +-- .../AI/Models/ChatCompletionToolCall.cs | 6 +- .../AI/Models/ChatMessage.cs | 30 +++---- .../AI/Models/MessageContext.cs | 10 ++- .../AI/Models/OpenAIModel.cs | 86 +++++++++++-------- .../AI/Planners/ActionPlanner.cs | 2 +- .../AI/Planners/AssistantsPlanner.cs | 28 +++--- .../AI/Prompts/PromptManager.cs | 2 +- .../AI/Prompts/PromptTemplate.cs | 6 +- .../AI/Prompts/Sections/LayoutSection.cs | 4 +- .../AI/Tokenizers/GPTTokenizer.cs | 4 +- .../Application/TeamsAttachmentDownloader.cs | 4 +- .../Microsoft.Teams.AI.csproj | 8 +- .../TeamsChefBot.csproj | 17 +--- .../06.assistants.a.mathBot/MathBot.csproj | 2 +- .../06.assistants.b.orderBot/OrderBot.csproj | 4 +- .../06.assistants.b.orderBot/Program.cs | 14 +-- getting-started/CONCEPTS/STREAMING.md | 1 + 29 files changed, 172 insertions(+), 207 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs index 9d2cb6595..31725bc8a 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AssistantMessageTests.cs @@ -15,7 +15,7 @@ public void Test_Constructor() { // Arrange MessageContent content = OpenAIModelFactory.CreateMessageContent("message", "fileId"); - Mock fileClientMock = new Mock(); + Mock fileClientMock = new Mock(); fileClientMock.Setup(fileClient => fileClient.DownloadFileAsync("fileId", It.IsAny())).Returns(() => { return Task.FromResult(ClientResult.FromValue(BinaryData.FromString("test"), new Mock().Object)); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs index 4c7b9a0fa..ab37f6541 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs @@ -48,7 +48,7 @@ public void Test_Initialization_From_OpenAISdk_ChatMessage() ""citations"": [ {{ ""title"": ""test-title"", - ""url"": ""test-url"", + ""url"": ""https://www.test-uri.com/"", ""content"": ""test-content"" }} ] @@ -69,7 +69,7 @@ public void Test_Initialization_From_OpenAISdk_ChatMessage() Assert.NotNull(context); Assert.Single(context.Citations); Assert.Equal("test-title", context.Citations[0].Title); - Assert.Equal("test-url", context.Citations[0].Url); + Assert.Equal("https://www.test-uri.com/", context.Citations[0].Url); Assert.Equal("test-content", context.Citations[0].Content); } @@ -179,10 +179,10 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_FunctionCall() // Assert var assistantMessage = result as AssistantChatMessage; Assert.NotNull(assistantMessage); - Assert.Equal("test-content", assistantMessage.Content[0].Text); + Assert.Empty(assistantMessage.Content); // TODO: Uncomment when participant name issue is resolved. //Assert.Equal("test-name", assistantMessage.ParticipantName); - Assert.Equal("test-arg1", assistantMessage.FunctionCall.FunctionArguments); + Assert.Equal("test-arg1", assistantMessage.FunctionCall.FunctionArguments.ToString()); Assert.Equal("test-name", assistantMessage.FunctionCall.FunctionName); } @@ -206,7 +206,7 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ActionCall() // Assert var assistantMessage = result as AssistantChatMessage; Assert.NotNull(assistantMessage); - Assert.Equal("test-content", assistantMessage.Content[0].Text); + Assert.Empty(assistantMessage.Content); // TODO: Uncomment when participant name issue is resolved. //Assert.Equal("test-name", assistantMessage.ParticipantName); @@ -215,7 +215,7 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ActionCall() Assert.NotNull(toolCall); Assert.Equal("test-id", toolCall.Id); Assert.Equal("test-tool-name", toolCall.FunctionName); - Assert.Equal("test-tool-arg1", toolCall.FunctionArguments); + Assert.Equal("test-tool-arg1", toolCall.FunctionArguments.ToString()); } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs index 0455a759e..7105c8693 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs @@ -11,7 +11,7 @@ public sealed class ChatCompletionToolCallTests public void Test_ChatCompletionsToolCall_ToFunctionToolCall() { // Arrange - var functionToolCall = ChatToolCall.CreateFunctionToolCall("test-id", "test-name", "test-arg1"); + var functionToolCall = ChatToolCall.CreateFunctionToolCall("test-id", "test-name", BinaryData.FromString("test-arg1")); // Act var azureSdkFunctionToolCall = ChatCompletionsToolCall.FromChatToolCall(functionToolCall); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs index ac7618e2c..a05156a69 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatMessageExtensionsTests.cs @@ -86,10 +86,10 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_FunctionCall() // Assert var assistantMessage = result as AssistantChatMessage; Assert.NotNull(assistantMessage); - Assert.Equal("test-content", assistantMessage.Content[0].Text); + Assert.Empty(assistantMessage.Content); // TODO: Uncomment when participant name issue is resolved. //Assert.Equal("test-name", assistantMessage.ParticipantName); - Assert.Equal("test-arg1", assistantMessage.FunctionCall.FunctionArguments); + Assert.Equal("test-arg1", assistantMessage.FunctionCall.FunctionArguments.ToString()); Assert.Equal("test-name", assistantMessage.FunctionCall.FunctionName); } @@ -113,14 +113,14 @@ public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall() // Assert var assistantMessage = result as AssistantChatMessage; Assert.NotNull(assistantMessage); - Assert.Equal("test-content", assistantMessage.Content[0].Text); + Assert.Empty(assistantMessage.Content); Assert.Single(assistantMessage.ToolCalls); ChatToolCall toolCall = assistantMessage.ToolCalls[0]; Assert.NotNull(toolCall); Assert.Equal("test-id", toolCall.Id); Assert.Equal("test-tool-name", toolCall.FunctionName); - Assert.Equal("test-tool-arg1", toolCall.FunctionArguments); + Assert.Equal("test-tool-arg1", toolCall.FunctionArguments.ToString()); } [Fact] @@ -198,7 +198,7 @@ public void Test_ChatCompletionsToolCall_ToFunctionToolCall() Assert.NotNull(chatToolCall); Assert.Equal("test-id", chatToolCall.Id); Assert.Equal("test-name", chatToolCall.FunctionName); - Assert.Equal("test-arg1", chatToolCall.FunctionArguments); + Assert.Equal("test-arg1", chatToolCall.FunctionArguments.ToString()); } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs index 1e9e1bb47..2b5b98f27 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs @@ -40,7 +40,7 @@ public void Test_Constructor_AzureOpenAI_InvalidAzureApiVersion() var options = new AzureOpenAIModelOptions("test-key", "test-deployment", "https://test.openai.azure.com/"); var versions = new List { - "2024-04-01-preview", "2024-05-01-preview", "2024-06-01" + "2024-06-01", "2024-08-01-preview", "2024-10-01-preview" }; // Act @@ -279,8 +279,8 @@ public async Task Test_CompletePromptAsync_AzureOpenAI_Chat_WithTools() Assert.NotNull(result.Message.ActionCalls); Assert.Single(result.Message.ActionCalls); - Assert.Equal("testAction", result.Message.ActionCalls[0].Function.Name); - + Assert.Equal("testAction", result.Message.ActionCalls[0].Function!.Name); + Assert.Null(result.Error); Assert.Equal(ChatRole.Assistant, result.Message.Role); Assert.Null(result.Message.Content); @@ -326,7 +326,7 @@ public async Task Test_CompletePromptAsync_AzureOpenAI_Streaming() ] }}")); - TestAsyncResultCollection updates = new(update!, Mock.Of()); + TestAsyncCollectionResult updates = new(update!, Mock.Of()); var response = new TestResponse(200, string.Empty); clientMock.Setup((client) => diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs index b7d84bc51..46a0a8ddc 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/OpenAIEmbeddingsTests.cs @@ -37,7 +37,7 @@ public async Task Test_OpenAI_CreateEmbeddings_ReturnEmbeddings() IList inputs = new List { "test" }; var clientMock = new Mock(new ApiKeyCredential(apiKey), It.IsAny()); var response = new TestResponse(200, string.Empty); - var embeddingCollection = ModelReaderWriter.Read(BinaryData.FromString(@"{ + var embeddingCollection = ModelReaderWriter.Read(BinaryData.FromString(@"{ ""data"": [ { ""object"": ""embedding"", @@ -76,7 +76,7 @@ public async Task Test_AzureOpenAI_CreateEmbeddings_ReturnEmbeddings() IList inputs = new List { "test" }; var clientMock = new Mock(new ApiKeyCredential(apiKey), It.IsAny()); var response = new TestResponse(200, string.Empty); - var embeddingCollection = ModelReaderWriter.Read(BinaryData.FromString(@"{ + var embeddingCollection = ModelReaderWriter.Read(BinaryData.FromString(@"{ ""data"": [ { ""object"": ""embedding"", diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj index e5e48327f..422955daf 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj @@ -11,13 +11,13 @@ - + - + diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs index fdff163f4..644611a5e 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/OpenAIModelFactory.cs @@ -1,7 +1,7 @@ using OpenAI.Assistants; -using OpenAI.Files; using System.ClientModel; using System.ClientModel.Primitives; +using OAI = OpenAI; namespace Microsoft.Teams.AI.Tests.TestUtils { @@ -89,7 +89,7 @@ public static MessageContent CreateMessageContent(string message, string fileId) return threadMessage.Content[0]; } - public static OpenAIFileInfo CreateOpenAIFileInfo(string fileId) + public static OAI.Files.OpenAIFile CreateOpenAIFileInfo(string fileId) { var json = @$"{{ ""id"": ""{fileId}"", @@ -100,7 +100,7 @@ public static OpenAIFileInfo CreateOpenAIFileInfo(string fileId) ""purpose"": ""assistants"" }}"; - var fileInfo = ModelReaderWriter.Read(BinaryData.FromString(json))!; + var fileInfo = ModelReaderWriter.Read(BinaryData.FromString(json))!; return fileInfo; } @@ -160,82 +160,40 @@ public TestRequiredAction(string toolCallId, string functionName, string functio } } - internal sealed class TestAsyncPageCollection : AsyncPageCollection where T : class + internal sealed class TestAsyncCollectionResult : AsyncCollectionResult where T : class { public List Items; internal PipelineResponse _pipelineResponse; - private IAsyncEnumerator> _enumerator; - public TestAsyncPageCollection(List items, PipelineResponse response) + public TestAsyncCollectionResult(List items, PipelineResponse response) { Items = items; _pipelineResponse = response; - _enumerator = new TestAsyncEnumerator(items, response); } - protected override IAsyncEnumerator> GetAsyncEnumeratorCore(CancellationToken cancellationToken = default) + public TestAsyncCollectionResult(T item, PipelineResponse response) { - return _enumerator; - } - - protected override Task> GetCurrentPageAsyncCore() - { - return Task.FromResult(_enumerator.Current); - } - } - - internal sealed class TestAsyncEnumerator : IAsyncEnumerator> where T : class - { - private readonly List _items; - private readonly PipelineResponse _pipelineResponse; - private bool _movedOnToNext; - - public TestAsyncEnumerator(List items, PipelineResponse response) - { - _items = items; + Items = new() { item }; _pipelineResponse = response; - _movedOnToNext = false; } - public PageResult Current => PageResult.Create(_items, ContinuationToken.FromBytes(BinaryData.FromString("")), null, _pipelineResponse); - - public ValueTask DisposeAsync() + public override ContinuationToken? GetContinuationToken(ClientResult page) { - return new ValueTask(); + return ContinuationToken.FromBytes(BinaryData.FromString("")); } - public ValueTask MoveNextAsync() + public async override IAsyncEnumerable GetRawPagesAsync() { - if (!_movedOnToNext) - { - return new ValueTask(true); - } - else - { - _movedOnToNext = true; - return new ValueTask(false); - } - + yield return await Task.FromResult(ClientResult.FromValue(Items, _pipelineResponse)); } - } - - internal sealed class TestAsyncResultCollection : AsyncCollectionResult where T : class - { - public List Items = new(); - internal PipelineResponse _pipelineResponse; - - public TestAsyncResultCollection(T item, PipelineResponse response) + protected async override IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) { - Items.Add(item); - _pipelineResponse = response; - } + foreach (T item in Items) + { + yield return await Task.FromResult(item); + } -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public override async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - yield return FromValue(Items[0], _pipelineResponse); } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs index 19d53a52e..bf908cc2c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/TestUtils/TestAssistantsOpenAIClient.cs @@ -76,7 +76,7 @@ private ThreadMessage _CreateMessage(string threadId, string message) return newMessage; } - public override AsyncPageCollection GetMessagesAsync(string threadId, MessageCollectionOptions options, CancellationToken cancellationToken = default) + public override AsyncCollectionResult GetMessagesAsync(string threadId, MessageCollectionOptions options, CancellationToken cancellationToken = default) { while (RemainingMessages.Count > 0) { @@ -86,12 +86,12 @@ public override AsyncPageCollection GetMessagesAsync(string threa // Sorted by oldest first List messages = Messages[threadId].ToList(); - if (options != null && options.Order != null && options.Order.Value == ListOrder.NewestFirst) + if (options != null && options.Order != null && options.Order.Value == MessageCollectionOrder.Descending) { messages.Reverse(); } - return new TestAsyncPageCollection(messages, Mock.Of()); + return new TestAsyncCollectionResult(messages, Mock.Of()); } public override Task> CreateRunAsync(string threadId, string assistantId, RunCreationOptions createRunOptions, CancellationToken cancellationToken = default) @@ -152,14 +152,14 @@ public override Task> GetRunAsync(string threadId, strin return runWithUpdatedStatus; } - public override AsyncPageCollection GetRunsAsync(string threadId, RunCollectionOptions? options = null, CancellationToken cancellationToken = default) + public override AsyncCollectionResult GetRunsAsync(string threadId, RunCollectionOptions? options = null, CancellationToken cancellationToken = default) { - AsyncPageCollection response; + AsyncCollectionResult response; // AssistantsPlanner only needs the get the latest. if (Runs[threadId].Count() == 0) { - response = new TestAsyncPageCollection(new List(), Mock.Of()); + response = new TestAsyncCollectionResult(new List(), Mock.Of()); return response; } @@ -167,7 +167,7 @@ public override AsyncPageCollection GetRunsAsync(string threadId, Run ThreadRun run = Runs[threadId][lastIndex]; ThreadRun runWithUpdatedStatus = _GetRun(threadId, run.Id)!; - response = new TestAsyncPageCollection(new List() { runWithUpdatedStatus }, Mock.Of()); + response = new TestAsyncCollectionResult(new List() { runWithUpdatedStatus }, Mock.Of()); return response; } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs index e0097d935..a77e789c2 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -368,7 +368,7 @@ CancellationToken cancellationToken repairTemplate.Prompt = new(new() { this.Options.Template.Prompt, - new ConversationHistorySection($"{this.Options.HistoryVariable}-repair") + new ConversationHistorySection($"{this.Options.HistoryVariable}-repair", -1) }); if (this.Options.LogRepairs) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Embeddings/OpenAIEmbeddings.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Embeddings/OpenAIEmbeddings.cs index 63e2b7b93..fb2c3a2ea 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Embeddings/OpenAIEmbeddings.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Embeddings/OpenAIEmbeddings.cs @@ -129,8 +129,8 @@ public async Task CreateEmbeddingsAsync(IList inputs try { DateTime startTime = DateTime.Now; - ClientResult response = await embeddingsClient.GenerateEmbeddingsAsync(inputs); - List> embeddingItems = response.Value.OrderBy(item => item.Index).Select(item => item.Vector).ToList(); + ClientResult response = await embeddingsClient.GenerateEmbeddingsAsync(inputs); + List> embeddingItems = response.Value.OrderBy(item => item.Index).Select(item => item.ToFloats()).ToList(); if (_options.LogRequests!.Value) { @@ -170,9 +170,9 @@ public async Task CreateEmbeddingsAsync(IList inputs { return apiVersion switch { - "2024-04-01-preview" => ServiceVersion.V2024_04_01_Preview, - "2024-05-01-preview" => ServiceVersion.V2024_05_01_Preview, "2024-06-01" => ServiceVersion.V2024_06_01, + "2024-08-01-preview" => ServiceVersion.V2024_08_01_Preview, + "2024-10-01-preview" => ServiceVersion.V2024_10_01_Preview, _ => null, }; } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs index 13a52336e..0d09229da 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/AssistantsMessage.cs @@ -2,6 +2,7 @@ using Microsoft.Bot.Schema; using OpenAI.Assistants; using OpenAI.Files; +using OAI = OpenAI; namespace Microsoft.Teams.AI.AI.Models @@ -26,7 +27,7 @@ public class AssistantsMessage : ChatMessage /// /// The Assistants API thread message. /// The OpenAI File client. - public AssistantsMessage(MessageContent content, FileClient? fileClient = null) : base(ChatRole.Assistant) + public AssistantsMessage(MessageContent content, OpenAIFileClient? fileClient = null) : base(ChatRole.Assistant) { this.MessageContent = content; @@ -39,7 +40,7 @@ public AssistantsMessage(MessageContent content, FileClient? fileClient = null) MessageContext context = new(); List>> fileContentDownloadTasks = new(); - List>> fileInfoDownloadTasks = new(); + List>> fileInfoDownloadTasks = new(); for (int i = 0; i < content.TextAnnotations.Count; i++) { @@ -73,7 +74,7 @@ public AssistantsMessage(MessageContent content, FileClient? fileClient = null) // Create attachments out of these downloaded files // Wait for tasks to complete ClientResult[] downloadedFileContent = fileContentDownloadTasks.Select((task) => task.Result).ToArray(); - ClientResult[] downloadedFileInfo = fileInfoDownloadTasks.Select((task) => task.Result).ToArray(); + ClientResult[] downloadedFileInfo = fileInfoDownloadTasks.Select((task) => task.Result).ToArray(); for (int i = 0; i < downloadedFileContent.Length; i++) { @@ -128,7 +129,7 @@ public class OpenAIFile /// /// Represents an OpenAI File information /// - public OpenAIFileInfo FileInfo; + public OAI.Files.OpenAIFile FileInfo; /// /// Represents the contents of an OpenAI File @@ -173,7 +174,7 @@ public class OpenAIFile /// /// The OpenAI File /// The OpenAI File contents - public OpenAIFile(OpenAIFileInfo fileInfo, BinaryData fileContent) + public OpenAIFile(OAI.Files.OpenAIFile fileInfo, BinaryData fileContent) { FileInfo = fileInfo; FileContent = fileContent; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs index d3a484d8c..1532893fc 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatCompletionToolCall.cs @@ -44,7 +44,7 @@ internal ChatToolCall ToChatToolCall() if (this.Type == ToolType.Function) { ChatCompletionsFunctionToolCall functionToolCall = (ChatCompletionsFunctionToolCall)this; - return ChatToolCall.CreateFunctionToolCall(functionToolCall.Id, functionToolCall.Name, functionToolCall.Arguments); + return ChatToolCall.CreateFunctionToolCall(functionToolCall.Id, functionToolCall.Name, BinaryData.FromString(functionToolCall.Arguments)); } throw new TeamsAIException($"Invalid tool type: {this.Type}"); @@ -60,7 +60,7 @@ internal static ChatCompletionsToolCall FromChatToolCall(ChatToolCall toolCall) { if (toolCall.Kind == ChatToolCallKind.Function) { - return new ChatCompletionsFunctionToolCall(toolCall.Id, toolCall.FunctionName, toolCall.FunctionArguments); + return new ChatCompletionsFunctionToolCall(toolCall.Id, toolCall.FunctionName, toolCall.FunctionArguments.ToString()); } throw new TeamsAIException($"Invalid ChatCompletionsToolCall type: {toolCall.GetType().Name}"); @@ -70,7 +70,7 @@ internal static ChatCompletionsToolCall FromStreamingChatToolCall(StreamingChatT { if (toolCall.Kind == ChatToolCallKind.Function) { - return new ChatCompletionsFunctionToolCall(toolCall.Id, toolCall.FunctionName, toolCall.FunctionArgumentsUpdate); + return new ChatCompletionsFunctionToolCall(toolCall.ToolCallId, toolCall.FunctionName, toolCall.FunctionArgumentsUpdate.ToString()); } throw new TeamsAIException($"Invalid ChatCompletionsToolCall type: {toolCall.GetType().Name}"); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs index 871ddfc49..1f0cc4d4f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/ChatMessage.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; -using Azure.AI.OpenAI; -using Azure.AI.OpenAI.Chat; +using Azure.AI.OpenAI.Chat; using Microsoft.Bot.Schema; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.Utilities; @@ -113,7 +111,7 @@ internal ChatMessage(ChatCompletion chatCompletion) if (chatCompletion.FunctionCall != null && chatCompletion.FunctionCall.FunctionName != string.Empty) { this.Name = chatCompletion.FunctionCall.FunctionName; - this.FunctionCall = new FunctionCall(chatCompletion.FunctionCall.FunctionName, chatCompletion.FunctionCall.FunctionArguments); + this.FunctionCall = new FunctionCall(chatCompletion.FunctionCall.FunctionName, chatCompletion.FunctionCall.FunctionArguments.ToString()); } if (chatCompletion.ToolCalls != null && chatCompletion.ToolCalls.Count > 0) @@ -127,7 +125,7 @@ internal ChatMessage(ChatCompletion chatCompletion) } #pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - AzureChatMessageContext? azureContext = chatCompletion.GetAzureMessageContext(); + ChatMessageContext? azureContext = chatCompletion.GetMessageContext(); #pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. if (azureContext != null) { @@ -155,7 +153,7 @@ internal ChatMessage(StreamingChatCompletionUpdate streamingChatCompletionUpdate if (streamingChatCompletionUpdate.FunctionCallUpdate != null && streamingChatCompletionUpdate.FunctionCallUpdate.FunctionName != string.Empty) { this.Name = streamingChatCompletionUpdate.FunctionCallUpdate.FunctionName; - this.FunctionCall = new FunctionCall(streamingChatCompletionUpdate.FunctionCallUpdate.FunctionName, streamingChatCompletionUpdate.FunctionCallUpdate.FunctionArgumentsUpdate); + this.FunctionCall = new FunctionCall(streamingChatCompletionUpdate.FunctionCallUpdate.FunctionName, streamingChatCompletionUpdate.FunctionCallUpdate.FunctionArgumentsUpdate.ToString()); } if (streamingChatCompletionUpdate.ToolCallUpdates != null && streamingChatCompletionUpdate.ToolCallUpdates.Count > 0) @@ -168,7 +166,7 @@ internal ChatMessage(StreamingChatCompletionUpdate streamingChatCompletionUpdate } #pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - AzureChatMessageContext? azureContext = streamingChatCompletionUpdate.GetAzureMessageContext(); + ChatMessageContext? azureContext = streamingChatCompletionUpdate.GetMessageContext(); #pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. if (azureContext != null) { @@ -204,12 +202,12 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() { if (contentPart is TextContentPart textPart) { - contentItems.Add(ChatMessageContentPart.CreateTextMessageContentPart(textPart.Text)); + contentItems.Add(ChatMessageContentPart.CreateTextPart(textPart.Text)); textContentBuilder.AppendLine(textPart.Text); } else if (contentPart is ImageContentPart imagePart) { - contentItems.Add(ChatMessageContentPart.CreateImageMessageContentPart(new Uri(imagePart.ImageUrl))); + contentItems.Add(ChatMessageContentPart.CreateImagePart(new Uri(imagePart.ImageUrl))); } } } @@ -245,8 +243,8 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() if (this.FunctionCall != null) { - ChatFunctionCall functionCall = new(this.FunctionCall.Name ?? "", this.FunctionCall.Arguments ?? ""); - assistantMessage = new AssistantChatMessage(functionCall, textContent); + ChatFunctionCall functionCall = new(this.FunctionCall.Name ?? "", BinaryData.FromString(this.FunctionCall.Arguments ?? "")); + assistantMessage = new AssistantChatMessage(functionCall); } else if (this.ActionCalls != null) { @@ -255,7 +253,7 @@ internal OAI.Chat.ChatMessage ToOpenAIChatMessage() { toolCalls.Add(actionCall.ToChatToolCall()); } - assistantMessage = new AssistantChatMessage(toolCalls, textContent); + assistantMessage = new AssistantChatMessage(toolCalls); } else { @@ -394,7 +392,7 @@ public ActionCall(ChatToolCall toolCall) } Id = toolCall.Id; - Function = new ActionFunction(toolCall.FunctionName, toolCall.FunctionArguments); + Function = new ActionFunction(toolCall.FunctionName, toolCall.FunctionArguments.ToString()); } /// @@ -409,15 +407,15 @@ public ActionCall(StreamingChatToolCallUpdate toolCall) throw new TeamsAIException($"Invalid ActionCall type: {toolCall.GetType().Name}"); } - Id = toolCall.Id; - Function = new ActionFunction(toolCall.FunctionName, toolCall.FunctionArgumentsUpdate); + Id = toolCall.ToolCallId; + Function = new ActionFunction(toolCall.FunctionName, toolCall.FunctionArgumentsUpdate.ToString()); } internal ChatToolCall ToChatToolCall() { if (this.Type == ActionCallType.Function) { - return ChatToolCall.CreateFunctionToolCall(Id, Function!.Name, Function.Arguments); + return ChatToolCall.CreateFunctionToolCall(Id, Function!.Name, BinaryData.FromString(Function.Arguments)); } throw new TeamsAIException($"Invalid tool type: {this.Type}"); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/MessageContext.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/MessageContext.cs index 10f808f4b..2b7c89534 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/MessageContext.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/MessageContext.cs @@ -27,14 +27,18 @@ public MessageContext() { } /// Creates a MessageContext using OpenAI.Chat.AzureChatMessageContext. /// /// - internal MessageContext(AzureChatMessageContext azureContext) +#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + internal MessageContext(ChatMessageContext azureContext) +#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. { if (azureContext.Citations != null) { - foreach (AzureChatCitation citation in azureContext.Citations) +#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + foreach (ChatCitation citation in azureContext.Citations) { - this.Citations.Add(new Citation(citation.Content, citation.Title, citation.Url)); + this.Citations.Add(new Citation(citation.Content, citation.Title, citation.Uri.ToString())); } +#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } this.Intent = azureContext.Intent; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs index b3fbad3c0..be7274c9d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs @@ -18,14 +18,33 @@ using ServiceVersion = Azure.AI.OpenAI.AzureOpenAIClientOptions.ServiceVersion; using Azure.AI.OpenAI.Chat; using OpenAI.Chat; -using Microsoft.Recognizers.Text.NumberWithUnit.Dutch; using Microsoft.Teams.AI.Application; +#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. namespace Microsoft.Teams.AI.AI.Models { /// - /// A `PromptCompletionModel` for calling OpenAI and Azure OpenAI hosted models. + /// A `IPromptCompletionModel` for calling OpenAI and Azure OpenAI hosted models. /// + /// + /// The model has been updated to support calling OpenAI's new o1 family of models. That currently + /// comes with a few constraints. These constraints are mostly handled for you but are worth noting: + /// + /// * The o1 models introduce a new `max_completion_tokens` parameter and they've deprecated the + /// `max_tokens` parameter. The model will automatically convert the incoming `max_tokens` parameter + /// to `max_completion_tokens` for you. But you should be aware that o1 has hidden token usage and costs + /// that aren't constrained by the `max_completion_tokens` parameter. This means that you may see an + /// increase in token usage and costs when using the o1 models. + /// + /// * The o1 models do not currently support the sending of system messages which just means that the + /// `useSystemMessages` parameter is ignored when calling the o1 models. + /// + /// * The o1 models do not currently support setting the `temperature`, `top_p`, and `presence_penalty` + /// parameters so they will be ignored. + /// + /// * The o1 models do not currently support the use of tools so you will need to use the "monologue" + /// augmentation to call actions. + /// public class OpenAIModel : IPromptCompletionStreamingModel { private readonly BaseOpenAIModelOptions _options; @@ -161,19 +180,6 @@ public async Task CompletePromptAsync(ITurnContext turnContext, Events.OnBeforeCompletion(beforeCompletionEventArgs); } - // Setup tools if enabled - bool isToolsAugmentation = promptTemplate.Configuration.Augmentation.Type == Augmentations.AugmentationType.Tools; - List tools = new(); - - // If tools is enabled, reformat actions to schema - if (isToolsAugmentation && promptTemplate.Actions.Count > 0) - { - foreach (ChatCompletionAction action in promptTemplate.Actions) - { - tools.Add(action.ToChatTool()); - } - } - // Render prompt RenderedPromptSection> prompt = await promptTemplate.Prompt.RenderAsMessagesAsync(turnContext, memory, promptFunctions, tokenizer, maxInputTokens, cancellationToken); if (prompt.TooLong) @@ -185,7 +191,11 @@ public async Task CompletePromptAsync(ITurnContext turnContext, }; } - if (!_options.UseSystemMessages!.Value && prompt.Output.Count > 0 && prompt.Output[0].Role == ChatRole.System) + // Get the model to use. + string model = promptTemplate.Configuration.Completion.Model ?? _deploymentName; + bool isO1Model = model.StartsWith("o1-"); + bool useSystemMessages = !isO1Model && _options.UseSystemMessages.GetValueOrDefault(false); + if (!useSystemMessages && prompt.Output.Count > 0 && prompt.Output[0].Role == ChatRole.System) { prompt.Output[0].Role = ChatRole.User; } @@ -196,42 +206,48 @@ public async Task CompletePromptAsync(ITurnContext turnContext, _logger.LogTrace(JsonSerializer.Serialize(prompt.Output, _serializerOptions)); } - // Render prompt template + // Map to OpenAI ChatMessage IEnumerable chatMessages = prompt.Output.Select(chatMessage => chatMessage.ToOpenAIChatMessage()); ChatCompletionOptions chatCompletionOptions = new() { - MaxTokens = completion.MaxTokens, + MaxOutputTokenCount = completion.MaxTokens, Temperature = (float)completion.Temperature, TopP = (float)completion.TopP, PresencePenalty = (float)completion.PresencePenalty, FrequencyPenalty = (float)completion.FrequencyPenalty, }; - if (isToolsAugmentation) + if (isO1Model) { - chatCompletionOptions.ToolChoice = completion.GetOpenAIChatToolChoice(); - chatCompletionOptions.ParallelToolCallsEnabled = completion.ParallelToolCalls; + chatCompletionOptions.Temperature = 1; + chatCompletionOptions.TopP = 1; + chatCompletionOptions.PresencePenalty = 0; } - foreach (ChatTool tool in tools) + // Set tools configurations + bool isToolsAugmentation = promptTemplate.Configuration.Augmentation.Type == Augmentations.AugmentationType.Tools; + if (isToolsAugmentation) { - chatCompletionOptions.Tools.Add(tool); - } - + chatCompletionOptions.ToolChoice = completion.GetOpenAIChatToolChoice(); + chatCompletionOptions.AllowParallelToolCalls = completion.ParallelToolCalls; - if (chatCompletionOptions == null) - { - throw new TeamsAIException("Failed to create chat completions options"); + if (promptTemplate.Actions.Count > 0) + { + foreach (ChatCompletionAction action in promptTemplate.Actions) + { + chatCompletionOptions.Tools.Add(action.ToChatTool()); + } + } } + // Add Azure chat extension configurations IDictionary? additionalData = promptTemplate.Configuration.Completion.AdditionalData; if (_useAzure) { AddAzureChatExtensionConfigurations(chatCompletionOptions, additionalData); } - string model = promptTemplate.Configuration.Completion.Model ?? _deploymentName; PipelineResponse? rawResponse = null; ClientResult? chatCompletionsResponse = null; @@ -319,7 +335,6 @@ public async Task CompletePromptAsync(ITurnContext turnContext, if (_options.LogRequests!.Value) { - // TODO: Colorize _logger.LogTrace("RESPONSE:"); _logger.LogTrace($"duration {(DateTime.UtcNow - startTime).TotalMilliseconds} ms"); if (promptResponse.Status == PromptResponseStatus.Success && chatCompletionsResponse != null) @@ -409,9 +424,9 @@ public async Task CompletePromptAsync(ITurnContext turnContext, { return apiVersion switch { - "2024-04-01-preview" => ServiceVersion.V2024_04_01_Preview, - "2024-05-01-preview" => ServiceVersion.V2024_05_01_Preview, "2024-06-01" => ServiceVersion.V2024_06_01, + "2024-08-01-preview" => ServiceVersion.V2024_08_01_Preview, + "2024-10-01-preview" => ServiceVersion.V2024_10_01_Preview, _ => null, }; } @@ -430,12 +445,10 @@ private void AddAzureChatExtensionConfigurations(ChatCompletionOptions options, { try { - AzureChatDataSource? dataSource = ModelReaderWriter.Read(BinaryData.FromObjectAsJson(item)); + ChatDataSource? dataSource = ModelReaderWriter.Read(BinaryData.FromObjectAsJson(item)); if (dataSource != null) { -#pragma warning disable AOAI001 options.AddDataSource(dataSource); -#pragma warning restore AOAI001 } } catch (Exception ex) @@ -446,4 +459,5 @@ private void AddAzureChatExtensionConfigurations(ChatCompletionOptions options, } } } -} \ No newline at end of file +} +#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs index f3751f6b4..8fb1432c6 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs @@ -108,7 +108,7 @@ public async Task ContinueTaskAsync(ITurnContext context, TState state, AI if (response.Status != PromptResponseStatus.Success) { - throw new Exception(response.Error?.Message ?? "[Action Planner]: an error has occurred"); + throw new Exception(response.Error?.Message ?? "[Action Planner]: an error has occurred", response.Error); } // Check to see if we have a response diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs index d352c3c26..b2f94fb24 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs @@ -8,7 +8,7 @@ using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.State; using Microsoft.Teams.AI.Utilities; -using OpenAI; +using OAI = OpenAI; using OpenAI.Assistants; using OpenAI.Files; using System.ClientModel; @@ -32,7 +32,7 @@ public class AssistantsPlanner : IPlanner private readonly AssistantsPlannerOptions _options; private readonly AssistantClient _client; - private readonly FileClient _fileClient; + private readonly OpenAIFileClient _fileClient; // TODO: Write trace logs #pragma warning disable IDE0052 // Remove unread private members @@ -189,14 +189,14 @@ private async Task _BlockOnInProgressRunsAsync(string threadId, CancellationToke // Loop until the last run is completed while (true) { - AsyncPageCollection? runs = _client.GetRunsAsync(threadId, new() { Order = ListOrder.NewestFirst }, cancellationToken); + AsyncCollectionResult? runs = _client.GetRunsAsync(threadId, new() { Order = RunCollectionOrder.Descending }, cancellationToken); if (runs == null) { return; } - ThreadRun? run = runs.GetAllValuesAsync().GetAsyncEnumerator().Current; + ThreadRun? run = runs.GetAsyncEnumerator().Current; if (run == null || _IsRunCompleted(run)) { return; @@ -210,9 +210,9 @@ private async Task _BlockOnInProgressRunsAsync(string threadId, CancellationToke private async Task _GeneratePlanFromMessagesAsync(string threadId, string lastMessageId, CancellationToken cancellationToken) { // Find the new messages - AsyncPageCollection messages = _client.GetMessagesAsync(threadId, new() { Order = ListOrder.NewestFirst }, cancellationToken); + AsyncCollectionResult messages = _client.GetMessagesAsync(threadId, new() { Order = MessageCollectionOrder.Descending }, cancellationToken); List newMessages = new(); - await foreach (ThreadMessage message in messages.GetAllValuesAsync()) + await foreach (ThreadMessage message in messages) { if (string.Equals(message.Id, lastMessageId)) { @@ -380,7 +380,7 @@ internal static AssistantClient _CreateClient(TokenCredential tokenCredential, s return azureOpenAI.GetAssistantClient(); } - internal FileClient _CreateFileClient(string apiKey, string? endpoint = null) + internal OpenAIFileClient _CreateFileClient(string apiKey, string? endpoint = null) { Verify.ParamNotNull(apiKey); @@ -388,22 +388,22 @@ internal FileClient _CreateFileClient(string apiKey, string? endpoint = null) { // Azure OpenAI AzureOpenAIClient azureOpenAI = new(new Uri(endpoint), new ApiKeyCredential(apiKey)); - return azureOpenAI.GetFileClient(); + return azureOpenAI.GetOpenAIFileClient(); } else { // OpenAI - return new FileClient(apiKey); + return new OpenAIFileClient(apiKey); } } - internal FileClient _CreateFileClient(TokenCredential tokenCredential, string endpoint) + internal OpenAIFileClient _CreateFileClient(TokenCredential tokenCredential, string endpoint) { Verify.ParamNotNull(tokenCredential); Verify.ParamNotNull(endpoint); AzureOpenAIClient azureOpenAI = new(new Uri(endpoint), tokenCredential); - return azureOpenAI.GetFileClient(); + return azureOpenAI.GetOpenAIFileClient(); } private async Task _CreateUserThreadMessageAsync(string threadId, TState state, CancellationToken cancellationToken) @@ -417,16 +417,16 @@ private async Task _CreateUserThreadMessageAsync(string threadId, IList? inputFiles = state.Temp?.InputFiles.Where((file) => file.Filename != null && file.Filename != string.Empty).ToList(); if (inputFiles != null && inputFiles.Count > 0) { - List>> fileUploadTasks = new(); + List>> fileUploadTasks = new(); foreach (InputFile file in inputFiles) { fileUploadTasks.Add(_fileClient.UploadFileAsync(file.Content, file.Filename!, FileUploadPurpose.Assistants)); } - ClientResult[] uploadedFiles = await Task.WhenAll(fileUploadTasks); + ClientResult[] uploadedFiles = await Task.WhenAll(fileUploadTasks); for (int i = 0; i < uploadedFiles.Count(); i++) { - OpenAIFileInfo file = uploadedFiles[i]; + OAI.Files.OpenAIFile file = uploadedFiles[i]; if (inputFiles[i].ContentType.StartsWith("image/")) { messages.Add(MessageContent.FromImageFileId(file.Id, MessageImageDetail.Auto)); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs index d7b8a6a8c..e495e4745 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptManager.cs @@ -128,7 +128,7 @@ public PromptTemplate GetPrompt(string name) template.Prompt.Sections = new List() { // The "1" place holder is to make this a fixed section so it is rendered in the correct order. // TODO: When implementing the new layout engine class refactor this. - new GroupSection(ChatRole.System, template.Prompt.Sections, 1) + new GroupSection(ChatRole.System, template.Prompt.Sections, 1) }; if (template.Configuration.Completion.IncludeHistory) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs index 8796b651b..7d342db1b 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/PromptTemplate.cs @@ -314,9 +314,9 @@ internal OAI.Chat.ChatToolChoice GetOpenAIChatToolChoice() { return ToolChoice switch { - ChatToolChoice.Auto => OAI.Chat.ChatToolChoice.Auto, - ChatToolChoice.Required => OAI.Chat.ChatToolChoice.Required, - ChatToolChoice.None => OAI.Chat.ChatToolChoice.None, + ChatToolChoice.Auto => OAI.Chat.ChatToolChoice.CreateAutoChoice(), + ChatToolChoice.Required => OAI.Chat.ChatToolChoice.CreateRequiredChoice(), + ChatToolChoice.None => OAI.Chat.ChatToolChoice.CreateNoneChoice(), _ => throw new InvalidOperationException($"Unknown ChatToolChoice: {ToolChoice}"), }; } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs index edaeb4603..2e8f43fc4 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Prompts/Sections/LayoutSection.cs @@ -20,7 +20,7 @@ private List _fixedSections { get { - return this.Sections.Where(s => s.Tokens > -1).OrderBy(s => s.Required).ToList(); + return this.Sections.Where(s => s.Tokens > -1).OrderBy(s => !s.Required).ToList(); } } @@ -28,7 +28,7 @@ private List _autoSections { get { - return this.Sections.Where(s => s.Tokens == -1).OrderBy(s => s.Required).ToList(); + return this.Sections.Where(s => s.Tokens == -1).OrderBy(s => !s.Required).ToList(); } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs index f24a07993..72b6e52cd 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs @@ -12,7 +12,7 @@ public class GPTTokenizer : ITokenizer /// /// Creates an instance of `GPTTokenizer` using "gpt-4" model name by default which is using the `cl100k_base` encoding /// - public GPTTokenizer() => _encoding = Tokenizer.CreateTiktokenForModel("gpt-4"); + public GPTTokenizer() => _encoding = TiktokenTokenizer.CreateForModel("gpt-4"); /// /// Creates an instance of `GPTTokenizer` @@ -24,7 +24,7 @@ public class GPTTokenizer : ITokenizer /// Creates an instance of `GPTTokenizer` /// /// model to encode/decode for - public GPTTokenizer(string model) => this._encoding = Tokenizer.CreateTiktokenForModel(model); + public GPTTokenizer(string model) => this._encoding = TiktokenTokenizer.CreateForModel("gpt-4"); /// /// Encode diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/TeamsAttachmentDownloader.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/TeamsAttachmentDownloader.cs index 887bce6af..9b46b0a07 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/TeamsAttachmentDownloader.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/TeamsAttachmentDownloader.cs @@ -39,8 +39,8 @@ public TeamsAttachmentDownloader(TeamsAttachmentDownloaderOptions options, HttpC public async Task> DownloadFilesAsync(ITurnContext turnContext, TState turnState, CancellationToken cancellationToken = default) { // Filter out HTML attachments - IEnumerable attachments = turnContext.Activity.Attachments.Where((a) => !a.ContentType.StartsWith("text/html")); - if (!attachments.Any()) + IEnumerable? attachments = turnContext.Activity.Attachments?.Where((a) => !a.ContentType.StartsWith("text/html")); + if (attachments == null || !attachments.Any()) { return new List(); } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index e0681fff3..bec3a1d80 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -37,7 +37,7 @@ - + @@ -45,9 +45,9 @@ - - - + + + diff --git a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj index 447ddbeac..5f8fa3c35 100644 --- a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj @@ -14,20 +14,9 @@ - - - - - - - - - - - - - - + + + diff --git a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj index 38a3405b4..30d0fc8d3 100644 --- a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj +++ b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj index 5c27d115c..bb37ee383 100644 --- a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj +++ b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/dotnet/samples/06.assistants.b.orderBot/Program.cs b/dotnet/samples/06.assistants.b.orderBot/Program.cs index 7f6c501bc..f13db0669 100644 --- a/dotnet/samples/06.assistants.b.orderBot/Program.cs +++ b/dotnet/samples/06.assistants.b.orderBot/Program.cs @@ -10,7 +10,6 @@ using OpenAI.Assistants; using Azure.Core; using Azure.Identity; -using System.Runtime.CompilerServices; using Microsoft.Teams.AI.Application; using OpenAI.Files; using OpenAI.VectorStores; @@ -86,14 +85,14 @@ // Create Vector Store var storeClient = client.GetVectorStoreClient(); - store = storeClient.CreateVectorStore(new VectorStoreCreationOptions()); + var storeCreationOperation = storeClient.CreateVectorStore(true); // Upload file. - var fileClient = client.GetFileClient(); + var fileClient = client.GetOpenAIFileClient(); var uploadedFile = fileClient.UploadFile("./assets/menu.pdf", FileUploadPurpose.Assistants); // Attach file to vector store - var fileAssociation = storeClient.AddFileToVectorStore(store, uploadedFile); + var fileAssociation = storeClient.AddFileToVectorStore(store.Id, uploadedFile.Value.Id, true); // Poll vector store until file is uploaded var maxPollCount = 5; @@ -113,10 +112,11 @@ } catch (Exception e) { - throw new Exception("Failed to upload file to vector store.", e); + throw new Exception("Failed to upload file to vector store.", e.InnerException); } - + var fileSearchTool = new FileSearchToolResources(); + fileSearchTool.VectorStoreIds.Add(store.Id); AssistantCreationOptions assistantCreationOptions = new() { Name = "Order Bot", @@ -129,7 +129,7 @@ }), ToolResources = new ToolResources() { - FileSearch = new FileSearchToolResources() { VectorStoreIds = new List() { store.Id } } + FileSearch = fileSearchTool } }; diff --git a/getting-started/CONCEPTS/STREAMING.md b/getting-started/CONCEPTS/STREAMING.md index c99ff131e..b96221ec0 100644 --- a/getting-started/CONCEPTS/STREAMING.md +++ b/getting-started/CONCEPTS/STREAMING.md @@ -60,6 +60,7 @@ Once `endStream()` is called, the stream is considered ended and no further upda - The informative message is rendered only at the beginning of each message returned from the LLM. - Attachments can only be sent in the final streamed chunk. - Streaming is not available in conjunction with AI SDK's function calls yet. +- Streaming does not work with OpenAI's `o1` models. ### Setup Instructions: From a56fec2837d8bdd7a5b36838e6d4d621fc496abf Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:37:29 -0700 Subject: [PATCH 02/18] [C#] fix: When streaming operation failed the exception is suppressed. (#2122) ## Linked issues closes: #minor ## Details Exceptions thrown in `DrainQueue()` are being supressed as the method runs on a worker thread and the corresponding `Task` is never resolved by the main thread. With the fix, when `this._queueSync` is awaited in `EndStream()`, an exception will be thrown if the corresponding `Task` is faulted (i.e. an exception is thrown from `DrainQueue()`. #### Change details * Removed code that sets `this._queueSync` to null when queue is empty. This means that `this._queueSync` can be either null or assigned a Task at any given time. * If `this._queueSync` is not null then an additional check `this._queueSync.isTaskCompleted` is made to check whether `DrainQueue` Task has completed or not. If streaming operation fails, the exception will be thrown: ![image](https://github.com/user-attachments/assets/403b6003-7df4-4684-b433-dd507b91cb4c) ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../Application/StreamingResponseTests.cs | 23 ++++++++++++++ .../Application/StreamingResponse.cs | 31 ++++++++----------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs index 5e8119b87..34d0a89ca 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs @@ -5,6 +5,7 @@ using Microsoft.Teams.AI.Application; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.Tests.TestUtils; +using Moq; namespace Microsoft.Teams.AI.Tests.Application { @@ -234,5 +235,27 @@ void CaptureSend(Activity[] arg) Assert.Equal(2, streamer.UpdatesSent()); Assert.Single(streamer.Attachments); } + + [Fact] + public async Task Test_SendActivityThrowsException_AssertThrows() + { + // Arrange + Activity[]? activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + var turnContextMock = new Mock(); + turnContextMock.Setup((tc) => tc.SendActivityAsync(It.IsAny(), It.IsAny())).ThrowsAsync(new Exception("Forbidden operation")); + + // Act + StreamingResponse streamer = new(turnContextMock.Object); + Exception ex = await Assert.ThrowsAsync(() => streamer.EndStream()); + + + // Assert + Assert.Equal("Error occured when sending activity while streaming: Forbidden operation", ex.Message); + } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs index cbaab401a..3420c0a99 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs @@ -121,7 +121,7 @@ public Task EndStream() QueueNextChunk(); // Wait for the queue to drain - return this._queueSync!; + return WaitForQueue()!; } /// @@ -133,7 +133,7 @@ private void QueueActivity(Func factory) this._queue.Add(factory); // If there's no sync in progress, start one - if (this._queueSync == null) + if (this._queueSync == null || this._queueSync.IsCompleted) { this._queueSync = DrainQueue(); } @@ -198,25 +198,20 @@ private void QueueNextChunk() /// private async Task DrainQueue() { - await Task.Run(async () => + try { - try + while (this._queue.Count > 0) { - while (this._queue.Count > 0) - { - // Get next activity from queue - Activity activity = _queue[0](); - await SendActivity(activity).ConfigureAwait(false); - _queue.RemoveAt(0); - } - } - - finally - { - // Queue is empty, mark as idle - this._queueSync = null; + // Get next activity from queue + Activity activity = _queue[0](); + await SendActivity(activity).ConfigureAwait(false); + _queue.RemoveAt(0); } - }).ConfigureAwait(false); + } + catch (Exception ex) + { + throw new TeamsAIException($"Error occured when sending activity while streaming: {ex.Message}", ex); + } } /// From b006d89a33c987dfbe8138fc58ec4476f9ee78d4 Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:31:30 -0700 Subject: [PATCH 03/18] [C#] fix: content safety public preview deprecation (#2138) ## Linked issues closes: #minor ## Details Deprecated public preview of azure content safety moderator. #### Change details * Updated `Azure.AI.ContentSafety` to `v1.0.0` * Added chat moderation sample ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../AzureContentSafetyModeratorTests.cs | 12 +- .../Moderator/AzureContentSafetyModerator.cs | 68 +++-- .../AzureContentSafetyModeratorOptions.cs | 10 +- .../Microsoft.Teams.AI.csproj | 2 +- .../samples/05.chatModeration/.editorconfig | 240 ++++++++++++++++++ dotnet/samples/05.chatModeration/.gitignore | 25 ++ .../05.chatModeration/ActionHandlers.cs | 31 +++ .../AdapterWithErrorHandler.cs | 26 ++ .../05.chatModeration/ChatModeration.csproj | 47 ++++ .../05.chatModeration/ChatModeration.sln | 25 ++ dotnet/samples/05.chatModeration/Config.cs | 29 +++ .../Controllers/BotController.cs | 32 +++ dotnet/samples/05.chatModeration/Program.cs | 128 ++++++++++ .../Prompts/Chat/config.json | 18 ++ .../Prompts/Chat/skprompt.txt | 3 + .../Properties/launchSettings.json | 27 ++ dotnet/samples/05.chatModeration/README.md | 77 ++++++ .../appPackage/manifest.json | 48 ++++ .../appsettings.Development.json | 21 ++ .../05.chatModeration/appsettings.json | 20 ++ .../05.chatModeration/assets/moderation.png | 3 + dotnet/samples/05.chatModeration/env/.env.dev | 18 ++ .../samples/05.chatModeration/env/.env.local | 12 + .../05.chatModeration/infra/azure.bicep | 113 +++++++++ .../infra/azure.parameters.json | 36 +++ .../infra/botRegistration/azurebot.bicep | 37 +++ .../infra/botRegistration/readme.md | 1 + .../05.chatModeration/teamsapp.local.yml | 87 +++++++ dotnet/samples/05.chatModeration/teamsapp.yml | 97 +++++++ 29 files changed, 1270 insertions(+), 23 deletions(-) create mode 100644 dotnet/samples/05.chatModeration/.editorconfig create mode 100644 dotnet/samples/05.chatModeration/.gitignore create mode 100644 dotnet/samples/05.chatModeration/ActionHandlers.cs create mode 100644 dotnet/samples/05.chatModeration/AdapterWithErrorHandler.cs create mode 100644 dotnet/samples/05.chatModeration/ChatModeration.csproj create mode 100644 dotnet/samples/05.chatModeration/ChatModeration.sln create mode 100644 dotnet/samples/05.chatModeration/Config.cs create mode 100644 dotnet/samples/05.chatModeration/Controllers/BotController.cs create mode 100644 dotnet/samples/05.chatModeration/Program.cs create mode 100644 dotnet/samples/05.chatModeration/Prompts/Chat/config.json create mode 100644 dotnet/samples/05.chatModeration/Prompts/Chat/skprompt.txt create mode 100644 dotnet/samples/05.chatModeration/Properties/launchSettings.json create mode 100644 dotnet/samples/05.chatModeration/README.md create mode 100644 dotnet/samples/05.chatModeration/appPackage/manifest.json create mode 100644 dotnet/samples/05.chatModeration/appsettings.Development.json create mode 100644 dotnet/samples/05.chatModeration/appsettings.json create mode 100644 dotnet/samples/05.chatModeration/assets/moderation.png create mode 100644 dotnet/samples/05.chatModeration/env/.env.dev create mode 100644 dotnet/samples/05.chatModeration/env/.env.local create mode 100644 dotnet/samples/05.chatModeration/infra/azure.bicep create mode 100644 dotnet/samples/05.chatModeration/infra/azure.parameters.json create mode 100644 dotnet/samples/05.chatModeration/infra/botRegistration/azurebot.bicep create mode 100644 dotnet/samples/05.chatModeration/infra/botRegistration/readme.md create mode 100644 dotnet/samples/05.chatModeration/teamsapp.local.yml create mode 100644 dotnet/samples/05.chatModeration/teamsapp.yml diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs index ed9b794f9..a3752d604 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/AzureContentSafetyModeratorTests.cs @@ -97,7 +97,8 @@ public async Task Test_ReviewPrompt_Flagged(ModerationType moderate) }; var clientMock = new Mock(new Uri(endpoint), new AzureKeyCredential(apiKey)); - AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(hateResult: ContentSafetyModelFactory.TextAnalyzeSeverityResult(TextCategory.Hate, 2)); + var analyses = new List() { ContentSafetyModelFactory.TextCategoriesAnalysis(TextCategory.Hate, 2) }; + AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(null, analyses); Response? response = null; clientMock.Setup(client => client.AnalyzeTextAsync(It.IsAny(), It.IsAny())).ReturnsAsync(Response.FromValue(analyzeTextResult, response)); @@ -173,7 +174,8 @@ public async Task Test_ReviewPrompt_NotFlagged(ModerationType moderate) }; var clientMock = new Mock(new Uri(endpoint), new AzureKeyCredential(apiKey)); - AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(hateResult: ContentSafetyModelFactory.TextAnalyzeSeverityResult(TextCategory.Hate, 0)); + var analyses = new List() { ContentSafetyModelFactory.TextCategoriesAnalysis(TextCategory.Hate, 0) }; + AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(null, analyses); Response? response = null; clientMock.Setup(client => client.AnalyzeTextAsync(It.IsAny(), It.IsAny())).ReturnsAsync(Response.FromValue(analyzeTextResult, response)); @@ -237,7 +239,8 @@ public async Task Test_ReviewPlan_Flagged(ModerationType moderate) }); var clientMock = new Mock(new Uri(endpoint), new AzureKeyCredential(apiKey)); - AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(hateResult: ContentSafetyModelFactory.TextAnalyzeSeverityResult(TextCategory.Hate, 2)); + var analyses = new List() { ContentSafetyModelFactory.TextCategoriesAnalysis(TextCategory.Hate, 2) }; + AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(null, analyses); Response? response = null; clientMock.Setup(client => client.AnalyzeTextAsync(It.IsAny(), It.IsAny())).ReturnsAsync(Response.FromValue(analyzeTextResult, response)); @@ -298,7 +301,8 @@ public async Task Test_ReviewPlan_NotFlagged(ModerationType moderate) }); var clientMock = new Mock(new Uri(endpoint), new AzureKeyCredential(apiKey)); - AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(hateResult: ContentSafetyModelFactory.TextAnalyzeSeverityResult(TextCategory.Hate, 0)); + var analyses = new List() { ContentSafetyModelFactory.TextCategoriesAnalysis(TextCategory.Hate, 0) }; + AnalyzeTextResult analyzeTextResult = ContentSafetyModelFactory.AnalyzeTextResult(null, analyses); Response? response = null; clientMock.Setup(client => client.AnalyzeTextAsync(It.IsAny(), It.IsAny())).ReturnsAsync(Response.FromValue(analyzeTextResult, response)); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModerator.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModerator.cs index c2a6efc44..896a49516 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModerator.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModerator.cs @@ -96,11 +96,8 @@ public async Task ReviewOutputAsync(ITurnContext turnContext, TState turnS { Response response = await _client.AnalyzeTextAsync(analyzeTextOptions); - bool flagged = response.Value.BlocklistsMatchResults.Count > 0 - || _ShouldBeFlagged(response.Value.HateResult) - || _ShouldBeFlagged(response.Value.SelfHarmResult) - || _ShouldBeFlagged(response.Value.SexualResult) - || _ShouldBeFlagged(response.Value.ViolenceResult); + bool flagged = response.Value.BlocklistsMatch.Count > 0 + || response.Value.CategoriesAnalysis.Any((ca) => _ShouldBeFlagged(ca)); if (flagged) { string actionName = isModelInput ? AIConstants.FlaggedInputActionName : AIConstants.FlaggedOutputActionName; @@ -138,17 +135,54 @@ public async Task ReviewOutputAsync(ITurnContext turnContext, TState turnS return null; } - private bool _ShouldBeFlagged(TextAnalyzeSeverityResult result) + private bool _ShouldBeFlagged(TextCategoriesAnalysis result) { return result != null && result.Severity >= _options.SeverityLevel; } private ModerationResult BuildModerationResult(AnalyzeTextResult result) { - bool hate = _ShouldBeFlagged(result.HateResult); - bool selfHarm = _ShouldBeFlagged(result.SelfHarmResult); - bool sexual = _ShouldBeFlagged(result.SexualResult); - bool violence = _ShouldBeFlagged(result.ViolenceResult); + bool hate = false; + int hateSeverity = 0; + bool selfHarm = false; + int selfHarmSeverity = 0; + bool sexual = false; + int sexualSeverity = 0; + bool violence = false; + int violenceSeverity = 0; + + foreach (TextCategoriesAnalysis textAnalysis in result.CategoriesAnalysis) + { + if (textAnalysis.Severity < _options.SeverityLevel) + { + continue; + } + + int severity = textAnalysis.Severity ?? 0; + if (textAnalysis.Category == TextCategory.Hate) + { + hate = true; + hateSeverity = severity; + } + + if (textAnalysis.Category == TextCategory.Violence) + { + violence = true; + violenceSeverity = severity; + } + + if (textAnalysis.Category == TextCategory.SelfHarm) + { + selfHarm = true; + selfHarmSeverity = severity; + } + + if (textAnalysis.Category == TextCategory.Sexual) + { + sexual = true; + sexualSeverity = severity; + } + } return new() { @@ -166,13 +200,13 @@ private ModerationResult BuildModerationResult(AnalyzeTextResult result) CategoryScores = new() { // Normalize the scores to be between 0 and 1 (highest severity is 6) - Hate = (result.HateResult?.Severity ?? 0) / 6.0, - HateThreatening = (result.HateResult?.Severity ?? 0) / 6.0, - SelfHarm = (result.SelfHarmResult?.Severity ?? 0) / 6.0, - Sexual = (result.SexualResult?.Severity ?? 0) / 6.0, - SexualMinors = (result.SexualResult?.Severity ?? 0) / 6.0, - Violence = (result.ViolenceResult?.Severity ?? 0) / 6.0, - ViolenceGraphic = (result.ViolenceResult?.Severity ?? 0) / 6.0 + Hate = hateSeverity / 6.0, + HateThreatening = hateSeverity / 6.0, + SelfHarm = selfHarmSeverity / 6.0, + Sexual = sexualSeverity / 6.0, + SexualMinors = sexualSeverity / 6.0, + Violence = violenceSeverity / 6.0, + ViolenceGraphic = violenceSeverity / 6.0 } }; } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModeratorOptions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModeratorOptions.cs index 6eae81577..143689dfe 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModeratorOptions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Moderator/AzureContentSafetyModeratorOptions.cs @@ -36,10 +36,18 @@ public class AzureContentSafetyModeratorOptions public IList? BlocklistNames { get; set; } /// - /// When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. + /// When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. + /// When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. /// + [Obsolete("use HaltOnBlockListHit")] public bool? BreakByBlocklists { get; set; } + /// + /// When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. + /// When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. + /// + public bool? HaltOnBlockListHit { get; set; } + /// /// Create an instance of the AzureContentSafetyModeratorOptions class. /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index bec3a1d80..f2c73fe7b 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -36,7 +36,7 @@ - + diff --git a/dotnet/samples/05.chatModeration/.editorconfig b/dotnet/samples/05.chatModeration/.editorconfig new file mode 100644 index 000000000..755bfa6c1 --- /dev/null +++ b/dotnet/samples/05.chatModeration/.editorconfig @@ -0,0 +1,240 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/.gitignore b/dotnet/samples/05.chatModeration/.gitignore new file mode 100644 index 000000000..d9db69b0e --- /dev/null +++ b/dotnet/samples/05.chatModeration/.gitignore @@ -0,0 +1,25 @@ +# TeamsFx files +build +appPackage/build +env/.env.*.user +env/.env.local +appsettings.Development.json +.deployment + +# User-specific files +*.user + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# VS files +.vs/ diff --git a/dotnet/samples/05.chatModeration/ActionHandlers.cs b/dotnet/samples/05.chatModeration/ActionHandlers.cs new file mode 100644 index 000000000..dd4a13067 --- /dev/null +++ b/dotnet/samples/05.chatModeration/ActionHandlers.cs @@ -0,0 +1,31 @@ +using Microsoft.Bot.Builder; +using Microsoft.Teams.AI.AI.Action; +using Microsoft.Teams.AI.AI; +using System.Text.Json; + +namespace ChatModeration +{ + public class ActionHandlers + { + private static JsonSerializerOptions _jsonSerializerOptions = new() + { + WriteIndented = true, + }; + + [Action(AIConstants.FlaggedInputActionName)] + public async Task OnFlaggedInput([ActionTurnContext] ITurnContext turnContext, [ActionParameters] Dictionary entities) + { + string entitiesJsonString = JsonSerializer.Serialize(entities, _jsonSerializerOptions); + await turnContext.SendActivityAsync($"I'm sorry your message was flagged:"); + await turnContext.SendActivityAsync($"```{entitiesJsonString}"); + return ""; + } + + [Action(AIConstants.FlaggedOutputActionName)] + public async Task OnFlaggedOutput([ActionTurnContext] ITurnContext turnContext) + { + await turnContext.SendActivityAsync("I'm not allowed to talk about such things."); + return ""; + } + } +} diff --git a/dotnet/samples/05.chatModeration/AdapterWithErrorHandler.cs b/dotnet/samples/05.chatModeration/AdapterWithErrorHandler.cs new file mode 100644 index 000000000..369476aba --- /dev/null +++ b/dotnet/samples/05.chatModeration/AdapterWithErrorHandler.cs @@ -0,0 +1,26 @@ +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Teams.AI; + +namespace ChatModeration +{ + public class AdapterWithErrorHandler : TeamsAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger) + : base(configuration, null, 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 unhandled error: {exception.Message}"); + await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); + // Send a trace activity + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/dotnet/samples/05.chatModeration/ChatModeration.csproj b/dotnet/samples/05.chatModeration/ChatModeration.csproj new file mode 100644 index 000000000..e5fc30c1d --- /dev/null +++ b/dotnet/samples/05.chatModeration/ChatModeration.csproj @@ -0,0 +1,47 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + None + + + + + ..\..\packages\Microsoft.TeamsAI\Microsoft.TeamsAI\obj\Debug\netstandard2.0\Microsoft.Teams.AI.dll + + + + + + diff --git a/dotnet/samples/05.chatModeration/ChatModeration.sln b/dotnet/samples/05.chatModeration/ChatModeration.sln new file mode 100644 index 000000000..c807cc153 --- /dev/null +++ b/dotnet/samples/05.chatModeration/ChatModeration.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33815.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatModeration", "ChatModeration.csproj", "{C2964D35-6742-4DBF-9685-5DD5A01D8D82}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C2964D35-6742-4DBF-9685-5DD5A01D8D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2964D35-6742-4DBF-9685-5DD5A01D8D82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2964D35-6742-4DBF-9685-5DD5A01D8D82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2964D35-6742-4DBF-9685-5DD5A01D8D82}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {30CCD595-AEBE-4CC2-B016-33E2EA023EAE} + EndGlobalSection +EndGlobal diff --git a/dotnet/samples/05.chatModeration/Config.cs b/dotnet/samples/05.chatModeration/Config.cs new file mode 100644 index 000000000..4080ba712 --- /dev/null +++ b/dotnet/samples/05.chatModeration/Config.cs @@ -0,0 +1,29 @@ +namespace ChatModeration +{ + public class ConfigOptions + { + public string? BOT_ID { get; set; } + public string? BOT_PASSWORD { get; set; } + public OpenAIConfigOptions? OpenAI { get; set; } + public AzureConfigOptions? Azure { get; set; } + } + + /// + /// Options for Open AI + /// + public class OpenAIConfigOptions + { + public string? ApiKey { get; set; } + } + + /// + /// Options for Azure OpenAI and Azure Content Safety + /// + public class AzureConfigOptions + { + public string? OpenAIApiKey { get; set; } + public string? OpenAIEndpoint { get; set; } + public string? ContentSafetyApiKey { get; set; } + public string? ContentSafetyEndpoint { get; set; } + } +} diff --git a/dotnet/samples/05.chatModeration/Controllers/BotController.cs b/dotnet/samples/05.chatModeration/Controllers/BotController.cs new file mode 100644 index 000000000..f0fbca52d --- /dev/null +++ b/dotnet/samples/05.chatModeration/Controllers/BotController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Teams.AI; + +namespace ChatModeration.Controllers +{ + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly TeamsAdapter _adapter; + private readonly IBot _bot; + + public BotController(TeamsAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + [HttpPost] + public async Task PostAsync(CancellationToken cancellationToken = default) + { + await _adapter.ProcessAsync + ( + Request, + Response, + _bot, + cancellationToken + ); + } + } +} diff --git a/dotnet/samples/05.chatModeration/Program.cs b/dotnet/samples/05.chatModeration/Program.cs new file mode 100644 index 000000000..cc0816c24 --- /dev/null +++ b/dotnet/samples/05.chatModeration/Program.cs @@ -0,0 +1,128 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Teams.AI.AI.Models; +using Microsoft.Teams.AI.AI.Planners; +using Microsoft.Teams.AI.AI.Prompts; +using Microsoft.Teams.AI.State; +using Microsoft.Teams.AI; +using ChatModeration; +using Microsoft.Teams.AI.AI.Moderator; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); +builder.Services.AddHttpContextAccessor(); + +// Prepare Configuration for ConfigurationBotFrameworkAuthentication +var config = builder.Configuration.Get()!; +builder.Configuration["MicrosoftAppType"] = "MultiTenant"; +builder.Configuration["MicrosoftAppId"] = config.BOT_ID; +builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; + +// Create the Bot Framework Authentication to be used with the Bot Adapter. +builder.Services.AddSingleton(); + +// Create the Cloud Adapter with error handling enabled. +// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so +// register the same adapter instance for all types. +builder.Services.AddSingleton(); +builder.Services.AddSingleton(sp => sp.GetService()!); +builder.Services.AddSingleton(sp => sp.GetService()!); + +builder.Services.AddSingleton(); + +// Create AI Model +if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey)) +{ + // Create OpenAI Model + builder.Services.AddSingleton(sp => new( + new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-4o") + { + LogRequests = true + }, + sp.GetService() + )); + + builder.Services.AddSingleton>(sp => new OpenAIModerator(new(apiKey: config.OpenAI.ApiKey, ModerationType.Both))); +} +else if (!string.IsNullOrEmpty(config.Azure?.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint)) +{ + // Create Azure OpenAI Model + builder.Services.AddSingleton(sp => new( + new AzureOpenAIModelOptions( + config.Azure.OpenAIApiKey, + "gpt-4o", + config.Azure.OpenAIEndpoint + ) + { + LogRequests = true + }, + sp.GetService() + )); + + builder.Services.AddSingleton>(sp => + new AzureContentSafetyModerator(new(config.Azure.OpenAIApiKey, config.Azure.OpenAIEndpoint, ModerationType.Both)) + ); +} +else +{ + throw new Exception("please configure settings for either OpenAI or Azure"); +} + +// Create the bot as transient. In this case the ASP Controller is expecting an IBot. +builder.Services.AddTransient(sp => +{ + // Create loggers + ILoggerFactory loggerFactory = sp.GetService()!; + + // Create Prompt Manager + PromptManager prompts = new(new() + { + PromptFolder = "./Prompts" + }); + + // Create ActionPlanner + ActionPlanner planner = new( + options: new( + model: sp.GetService()!, + prompts: prompts, + defaultPrompt: async (context, state, planner) => + { + PromptTemplate template = prompts.GetPrompt("Chat"); + return await Task.FromResult(template); + } + ) + { LogRepairs = true }, + loggerFactory: loggerFactory + ); + + Application app = new ApplicationBuilder() + .WithAIOptions(new(planner) { Moderator = sp.GetService>() }) + .WithStorage(sp.GetService()!) + .Build(); + + app.AI.ImportActions(new ActionHandlers()); + + app.OnConversationUpdate("membersAdded", async (context, state, token) => + { + await context.SendActivityAsync("Hello and welcome! With this sample you can see the functionality of the Azure AI Content Safety Moderator " + + "or OpenAI's Moderator based on the setup configurations."); + }); + + return app; +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseStaticFiles(); +app.UseRouting(); +app.MapControllers(); + +app.Run(); diff --git a/dotnet/samples/05.chatModeration/Prompts/Chat/config.json b/dotnet/samples/05.chatModeration/Prompts/Chat/config.json new file mode 100644 index 000000000..fd6e274a4 --- /dev/null +++ b/dotnet/samples/05.chatModeration/Prompts/Chat/config.json @@ -0,0 +1,18 @@ +{ + "schema": 1.1, + "description": "A bot that is configured to use chat moderation", + "type": "completion", + "completion": { + "model": "gpt-4o", + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2000, + "max_tokens": 1000, + "temperature": 0.2, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + } +} \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/Prompts/Chat/skprompt.txt b/dotnet/samples/05.chatModeration/Prompts/Chat/skprompt.txt new file mode 100644 index 000000000..bc17ef457 --- /dev/null +++ b/dotnet/samples/05.chatModeration/Prompts/Chat/skprompt.txt @@ -0,0 +1,3 @@ +You are the AI assistant demonstrating the Azure OpenAI's content safety moderation capabilities. +The following is a conversation with an AI assistant. +You evaluate the moderation severity of human's input in the following categories of moderation: hate, sexual content, self harm, violence. \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/Properties/launchSettings.json b/dotnet/samples/05.chatModeration/Properties/launchSettings.json new file mode 100644 index 000000000..9efe20cd7 --- /dev/null +++ b/dotnet/samples/05.chatModeration/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "profiles": { + // Debug project within Teams + "Microsoft Teams (browser)": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "https://teams.microsoft.com/l/app/576b3387-9ef7-4aff-9da7-acc2ad2f6d0f?installAppPackage=true&webjoin=true&appTenantId=d247b24d-59a3-4042-8253-90aa371a6eb4&login_hint=kavinsingh_microsoft.com", + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "hotReloadProfile": "aspnetcore" + } + //// Uncomment following profile to debug project only (without launching Teams) + //, + //"Start Project (not in Teams)": { + // "commandName": "Project", + // "dotnetRunMessages": true, + // "applicationUrl": "https://localhost:7130;http://localhost:5130", + // "environmentVariables": { + // "ASPNETCORE_ENVIRONMENT": "Development" + // }, + // "hotReloadProfile": "aspnetcore" + //} + } +} \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/README.md b/dotnet/samples/05.chatModeration/README.md new file mode 100644 index 000000000..67a9001b5 --- /dev/null +++ b/dotnet/samples/05.chatModeration/README.md @@ -0,0 +1,77 @@ +# Chat Bot with Moderation Control + +## Summary + +This sample shows how to incorporate Content Safety control into a Microsoft Teams application. + +## 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). + +Note that, this sample requires AI service so you need one more pre-step before Local Debug (F5). + +1. Set your Azure OpenAI related settings to *appsettings.Development.json*. + + ```json + "Azure": { + "OpenAIApiKey": "", + "OpenAIEndpoint": "", + "ContentSafetyApiKey": "", + "ContentSafetyEndpoint": "" + } + ``` + +## Interacting with the bot + +You can interact with this bot by sending it a message. If you send it a message that contains inappropriate content, the bot will response with a moderation report that contains the inappropriate content: + +![Moderation Report](./assets/moderation.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). + +Note that, this sample requires AI service so you need one more pre-step before deploy to Azure. To configure the Azure resources to have an environment variable for the Azure OpenAI Key and other settings: + +1. In `./env/.env.dev.user` file, paste your Azure OpenAI related variables. + + ```bash + SECRET_AZURE_OPENAI_API_KEY= + SECRET_AZURE_OPENAI_ENDPOINT= + SECRET_AZURE_CONTENT_SAFETY_API_KEY= + SECRET_AZURE_CONTENT_SAFETY_ENDPOINT= + ``` + +The `SECRET_` prefix is a convention used by Teams Toolkit to mask the value in any logging output and is optional. + +## Use OpenAI + +Above steps use Azure OpenAI as AI service, optionally, you can also use OpenAI as AI service. + +**As prerequisites** + +1. Get an OpenAI api key. + +**For debugging (F5)** + +1. Set your [OpenAI API Key](https://platform.openai.com/settings/profile?tab=api-keys) to *appsettings.Development.json*. + + ```json + "OpenAI": { + "ApiKey": "" + }, + ``` + +**For deployment to Azure** + +To configure the Azure resources to have OpenAI environment variables: + +1. In `./env/.env.dev.user` file, paste your [OpenAI API Key](https://platform.openai.com/settings/profile?tab=api-keys) to the environment variable `SECRET_OPENAI_KEY=`. + +## Further reading + +- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) +- [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) diff --git a/dotnet/samples/05.chatModeration/appPackage/manifest.json b/dotnet/samples/05.chatModeration/appPackage/manifest.json new file mode 100644 index 000000000..81f3ef407 --- /dev/null +++ b/dotnet/samples/05.chatModeration/appPackage/manifest.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.15/MicrosoftTeams.schema.json", + "version": "1.1.0", + "manifestVersion": "1.15", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.package.name", + "name": { + "short": "Moderation${{APP_NAME_SUFFIX}}", + "full": "Moderation Bot" + }, + "developer": { + "name": "Moderation", + "mpnId": "", + "websiteUrl": "https://microsoft.com", + "privacyUrl": "https://privacy.microsoft.com/privacystatement", + "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" + }, + "description": { + "short": "Sample bot that thinks it's a Chef to help you cook Teams apps", + "full": "Sample bot that thinks it's a Chef to help you cook Teams apps" + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#FFFFFF", + "staticTabs": [ + { + "entityId": "conversations", + "scopes": ["personal"] + }, + { + "entityId": "about", + "scopes": ["personal"] + } + ], + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": ["personal", "team", "groupChat"], + "isNotificationOnly": false, + "supportsCalling": false, + "supportsVideo": false, + "supportsFiles": false + } + ], + "validDomains": [] +} diff --git a/dotnet/samples/05.chatModeration/appsettings.Development.json b/dotnet/samples/05.chatModeration/appsettings.Development.json new file mode 100644 index 000000000..1928e121f --- /dev/null +++ b/dotnet/samples/05.chatModeration/appsettings.Development.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Teams.AI": "Trace" + } + }, + "AllowedHosts": "*", + "BOT_ID": "${botId}", + "BOT_PASSWORD": "${botPassword}", + "Azure": { + "OpenAIApiKey": "", + "OpenAIEndpoint": "", + "ContentSafetyApiKey": "", + "ContentSafetyEndpoint": "" + }, + "OpenAI": { + "ApiKey": "" + } +} \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/appsettings.json b/dotnet/samples/05.chatModeration/appsettings.json new file mode 100644 index 000000000..9ac767903 --- /dev/null +++ b/dotnet/samples/05.chatModeration/appsettings.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "BOT_ID": "${botId}", + "BOT_PASSWORD": "${botPassword}", + "Azure": { + "OpenAIApiKey": "", + "OpenAIEndpoint": "", + "ContentSafetyApiKey": "", + "ContentSafetyEndpoint": "" + }, + "OpenAI": { + "ApiKey": "" + } +} diff --git a/dotnet/samples/05.chatModeration/assets/moderation.png b/dotnet/samples/05.chatModeration/assets/moderation.png new file mode 100644 index 000000000..92f262e67 --- /dev/null +++ b/dotnet/samples/05.chatModeration/assets/moderation.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8c34c851b963ccdc677e158ea8e96eaecec80a0cfdb0800fcc7f6e929fcb2f5 +size 44202 diff --git a/dotnet/samples/05.chatModeration/env/.env.dev b/dotnet/samples/05.chatModeration/env/.env.dev new file mode 100644 index 000000000..efcbe1f06 --- /dev/null +++ b/dotnet/samples/05.chatModeration/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= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= + + +APP_NAME_SUFFIX=dev diff --git a/dotnet/samples/05.chatModeration/env/.env.local b/dotnet/samples/05.chatModeration/env/.env.local new file mode 100644 index 000000000..07b69ee56 --- /dev/null +++ b/dotnet/samples/05.chatModeration/env/.env.local @@ -0,0 +1,12 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= + + +APP_NAME_SUFFIX=local \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/infra/azure.bicep b/dotnet/samples/05.chatModeration/infra/azure.bicep new file mode 100644 index 000000000..e3877021b --- /dev/null +++ b/dotnet/samples/05.chatModeration/infra/azure.bicep @@ -0,0 +1,113 @@ +@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 + +@secure() +param openAIApiKey string + +@secure() +param azureOpenAIApiKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureContentSafetyApiKey string + +@secure() +param azureContentSafetyEndpoint 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: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + { + name: 'OpenAI__ApiKey' + value: openAIApiKey + } + { + name: 'Azure__OpenAIApiKey' + value: azureOpenAIApiKey + } + { + name: 'Azure__OpenAIEndpoint' + value: azureOpenAIEndpoint + } + { + name: 'Azure__ContentSafetyApiKey' + value: azureContentSafetyApiKey + } + { + name: 'Azure__ContentSafetyEndpoint' + value: azureContentSafetyEndpoint + } + ] + 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/dotnet/samples/05.chatModeration/infra/azure.parameters.json b/dotnet/samples/05.chatModeration/infra/azure.parameters.json new file mode 100644 index 000000000..00e948732 --- /dev/null +++ b/dotnet/samples/05.chatModeration/infra/azure.parameters.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "moderationbot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "ModerationBot" + }, + "openAIApiKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + "azureOpenAIApiKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{SECRET_AZURE_OPENAI_ENDPOINT}}" + }, + "azureContentSafetyApiKey": { + "value": "${{SECRET_AZURE_CONTENT_SAFETY_API_KEY}}" + }, + "azureContentSafetyEndpoint": { + "value": "${{SECRET_AZURE_CONTENT_SAFETY_ENDPOINT}}" + } + } +} \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/infra/botRegistration/azurebot.bicep b/dotnet/samples/05.chatModeration/infra/botRegistration/azurebot.bicep new file mode 100644 index 000000000..ab67c7a56 --- /dev/null +++ b/dotnet/samples/05.chatModeration/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/dotnet/samples/05.chatModeration/infra/botRegistration/readme.md b/dotnet/samples/05.chatModeration/infra/botRegistration/readme.md new file mode 100644 index 000000000..d5416243c --- /dev/null +++ b/dotnet/samples/05.chatModeration/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. \ No newline at end of file diff --git a/dotnet/samples/05.chatModeration/teamsapp.local.yml b/dotnet/samples/05.chatModeration/teamsapp.local.yml new file mode 100644 index 000000000..c1950a386 --- /dev/null +++ b/dotnet/samples/05.chatModeration/teamsapp.local.yml @@ -0,0 +1,87 @@ +# 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 selecting 'Prepare Teams App Dependencies' menu items in the Teams Toolkit for Visual Studio window +# +# You can customize this file. Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +version: 1.1.0 + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +provision: + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + # Teams app name + name: Moderation${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + 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: + # The Azure Active Directory application's display name + name: Moderation + writeToEnvironmentFile: + # The Azure Active Directory application's client id created for bot. + botId: BOT_ID + # The Azure Active Directory application's client secret created for bot. + botPassword: 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. + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: Moderation + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Generate runtime appsettings to JSON file + - uses: file/createOrUpdateJsonFile + with: + target: ./appsettings.Development.json + content: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + + # Optional: Automates schema and error checking of the Teams app manifest and outputs the results in the console. + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Automates the creation of a Teams app package (.zip). + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # 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: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Create or update debug profile in lauchsettings file + - 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/dotnet/samples/05.chatModeration/teamsapp.yml b/dotnet/samples/05.chatModeration/teamsapp.yml new file mode 100644 index 000000000..9af915ea1 --- /dev/null +++ b/dotnet/samples/05.chatModeration/teamsapp.yml @@ -0,0 +1,97 @@ +# 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 selecting 'Provision' or 'Deploy' menu items in the Teams Toolkit for Visual Studio window +# +# You can customize this file. Visit https://aka.ms/teamsfx-v5.0-guide for more info about Teams Toolkit project files. +version: 1.1.0 + +environmentFolderPath: ./env + +# Defines what the `provision` lifecycle step does with Teams Toolkit. +provision: + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + # Teams app name + name: Moderation${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + 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: + # The Azure Active Directory application's display name + name: Moderation + writeToEnvironmentFile: + # The Azure Active Directory application's client id created for bot. + botId: BOT_ID + # The Azure Active Directory application's client secret created for bot. + 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 # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-tab + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + 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: + # Path to manifest template + 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: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + + # 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: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsfx deploy' is executed +deploy: + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + + # Deploy to an Azure App Service using the zip file created in the provision step. + - uses: azureAppService/zipDeploy + with: + # deploy base folder + artifactFolder: bin/Release/net6.0/win-x86/publish + # This example uses the env var thats generated by the arm/deploy action. + # You can replace it with an existing Azure Resource ID or other + # custom environment variable. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} From 0c7375f4e3160eeb49deada754b7185e3ce730e6 Mon Sep 17 00:00:00 2001 From: Lily Du Date: Wed, 23 Oct 2024 14:59:08 -0700 Subject: [PATCH 04/18] [C#] feat: Generated by AI Label, Feedback Loop, Streaming Buffer, Error Propagation, Entities Metadata (#2139) ## Linked issues closes: #1970 ## Details - Added temporary 1.5 second buffer to adhere to 1RPS backend service requirement - Added support for Feedback Loop - Added support for Generated by AI label - Added reject/catch handling for errors - Added entities metadata to match GA requirements **screenshots**: ![image](https://github.com/user-attachments/assets/78eb652e-65c2-4544-9e30-36d656a8a9e3) ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --------- Co-authored-by: lilydu --- .../AITests/ActionPlannerTests.cs | 2 +- .../AITests/LLMClientTests.cs | 1 + .../Application/StreamingResponseTests.cs | 31 ++++++++++- .../Microsoft.TeamsAI/AI/AI.cs | 1 + .../Microsoft.TeamsAI/AI/Clients/LLMClient.cs | 10 ++++ .../AI/Clients/LLMClientOptions.cs | 5 ++ .../AI/Planners/ActionPlanner.cs | 6 +++ .../Application/StreamingChannelData.cs | 7 +++ .../Application/StreamingResponse.cs | 52 ++++++++++++++++++- .../04.ai.g.teamsChefBot-streaming/Program.cs | 8 ++- 10 files changed, 119 insertions(+), 4 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs index 38be75e91..47cdcce5c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ActionPlannerTests.cs @@ -328,7 +328,7 @@ public async Task Test_ContinueTaskAsync_Streaming() await state.LoadStateAsync(null, turnContext); state.Temp.Input = "test"; var planner = new ActionPlanner(options, new TestLoggerFactory()); - var ai = new AI(new(planner)); + var ai = new AI(new(planner) { EnableFeedbackLoop = true }); // Act var result = await planner.ContinueTaskAsync(turnContext, state, ai); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs index 7c1a51de6..4db309d8f 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs @@ -176,6 +176,7 @@ public async Task Test_CompletePromptAsync_Streaming_Success() { StartStreamingMessage = "Begin streaming", EndStreamHandler = handler, + EnableFeedbackLoop = true, }; LLMClient client = new(options, null); TestMemory memory = new(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs index 34d0a89ca..5e65180ed 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs @@ -5,6 +5,7 @@ using Microsoft.Teams.AI.Application; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.Tests.TestUtils; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Moq; namespace Microsoft.Teams.AI.Tests.Application @@ -197,6 +198,34 @@ void CaptureSend(Activity[] arg) Assert.Equal(2, streamer.UpdatesSent()); } + [Fact] + public async Task Test_SendTextChunk_SendsFinalMessageWithPoweredByAIFeatures() + { + // Arrange + Activity[]? activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + var adapter = new SimpleAdapter(CaptureSend); + ITurnContext turnContext = new TurnContext(adapter, new Activity( + text: "hello", + channelId: "channelId", + recipient: new() { Id = "recipientId" }, + conversation: new() { Id = "conversationId" }, + from: new() { Id = "fromId" } + )); + StreamingResponse streamer = new(turnContext); + streamer.QueueTextChunk("first"); + await streamer.WaitForQueue(); + streamer.QueueTextChunk("second"); + await streamer.WaitForQueue(); + streamer.EnableFeedbackLoop = true; + streamer.EnableGeneratedByAILabel = true; + await streamer.EndStream(); + Assert.Equal(2, streamer.UpdatesSent()); + } + [Fact] public async Task Test_SendTextChunk_SendsFinalMessageWithAttachments() { @@ -255,7 +284,7 @@ void CaptureSend(Activity[] arg) // Assert - Assert.Equal("Error occured when sending activity while streaming: Forbidden operation", ex.Message); + Assert.Equal("Error occurred when sending activity while streaming", ex.Message); } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs index ddd224933..6a3057a77 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/AI.cs @@ -36,6 +36,7 @@ public AI(AIOptions options, ILoggerFactory? loggerFactory = null) MaxSteps = options.MaxSteps ?? 25, MaxTime = options.MaxTime ?? TimeSpan.FromMilliseconds(300000), AllowLooping = options.AllowLooping ?? true, + EnableFeedbackLoop = options.EnableFeedbackLoop, }; _actions = new ActionCollection(); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs index a77e789c2..3eb6c52d3 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -66,6 +66,7 @@ public class LLMClient private readonly string? _startStreamingMessage; private ResponseReceivedHandler? _endStreamHandler; + private bool? _enableFeedbackLoop; /// /// Creates a new `LLMClient` instance. @@ -84,6 +85,7 @@ public LLMClient(LLMClientOptions options, ILoggerFactory? loggerFacto this._startStreamingMessage = Options.StartStreamingMessage; this._endStreamHandler = Options.EndStreamHandler; + this._enableFeedbackLoop = Options.EnableFeedbackLoop; } /// @@ -171,6 +173,14 @@ public async Task CompletePromptAsync( // Create streamer and send initial message streamer = new StreamingResponse(context); memory.SetValue("temp.streamer", streamer); + + if (this._enableFeedbackLoop != null) + { + streamer.EnableFeedbackLoop = this._enableFeedbackLoop; + } + + streamer.EnableGeneratedByAILabel = true; + if (!string.IsNullOrEmpty(this._startStreamingMessage)) { streamer.QueueInformativeUpdate(this._startStreamingMessage!); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs index e9e47050b..e5e7d7d6a 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClientOptions.cs @@ -73,6 +73,11 @@ public class LLMClientOptions /// public ResponseReceivedHandler? EndStreamHandler; + /// + /// Optional, controls the feedback loop for streaming responses. + /// + public bool? EnableFeedbackLoop { get; set; } + /// /// Creates an instance of `LLMClientOptions` /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs index 8fb1432c6..e60c1f0bc 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/ActionPlanner.cs @@ -38,6 +38,8 @@ public class ActionPlanner : IPlanner where TState : TurnState private readonly ILoggerFactory? _logger; + private bool _enableFeedbackLoop; + /// /// Creates a new `ActionPlanner` instance. /// @@ -104,6 +106,9 @@ public async Task BeginTaskAsync(ITurnContext context, TState state, AI ContinueTaskAsync(ITurnContext context, TState state, AI ai, CancellationToken cancellationToken = default) { PromptTemplate template = await this.Options.DefaultPrompt(context, state, this); + + this._enableFeedbackLoop = ai.Options.EnableFeedbackLoop; + PromptResponse response = await this.CompletePromptAsync(context, state, template, template.Augmentation, cancellationToken); if (response.Status != PromptResponseStatus.Success) @@ -176,6 +181,7 @@ public async Task CompletePromptAsync( LogRepairs = this.Options.LogRepairs, StartStreamingMessage = this.Options.StartStreamingMessage, EndStreamHandler = this.Options.EndStreamHandler, + EnableFeedbackLoop = this._enableFeedbackLoop, }, this._logger); return await client.CompletePromptAsync(context, memory, this.Prompts, cancellationToken); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs index 3c79fa397..fa675a280 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingChannelData.cs @@ -34,5 +34,12 @@ public class StreamingChannelData /// [JsonProperty(PropertyName = "streamId")] public string? streamId { get; set; } + + /// + /// Sets the Feedback Loop in Teams that allows a user to + /// give thumbs up or down to a response. + /// + [JsonProperty(PropertyName = "feedbackLoopEnabled")] + public bool? feedbackLoopEnabled { get; set; } } } diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs index 3420c0a99..fb844c5c5 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs @@ -1,6 +1,8 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; +using Microsoft.Teams.AI.AI.Action; using Microsoft.Teams.AI.Exceptions; +using Newtonsoft.Json.Linq; namespace Microsoft.Teams.AI.Application { @@ -30,6 +32,18 @@ public class StreamingResponse /// 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. @@ -136,6 +150,13 @@ private void QueueActivity(Func factory) 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); + } } } @@ -210,7 +231,7 @@ private async Task DrainQueue() } catch (Exception ex) { - throw new TeamsAIException($"Error occured when sending activity while streaming: {ex.Message}", ex); + throw ex; } } @@ -239,8 +260,37 @@ private async Task SendActivity(Activity activity) activity.ChannelData = updatedChannelData; } + activity.Entities = new List{ + new Entity("streaminfo") + { + Properties = JObject.FromObject(new { + streamId = ((StreamingChannelData) activity.ChannelData).streamId, + streamType = ((StreamingChannelData) activity.ChannelData).StreamType, + streamSequence = ((StreamingChannelData) activity.ChannelData).StreamSequence, + + }) + } + }; + + // 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) + { + activity.Entities.Add(new AIEntity()); + } + } + ResourceResponse response = await this._context.SendActivityAsync(activity).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(1.5)); + // Save assigned stream ID if (string.IsNullOrEmpty(StreamId)) { diff --git a/dotnet/samples/04.ai.g.teamsChefBot-streaming/Program.cs b/dotnet/samples/04.ai.g.teamsChefBot-streaming/Program.cs index 3df8d3f53..0a439e50d 100644 --- a/dotnet/samples/04.ai.g.teamsChefBot-streaming/Program.cs +++ b/dotnet/samples/04.ai.g.teamsChefBot-streaming/Program.cs @@ -164,12 +164,18 @@ ); Application app = new ApplicationBuilder() - .WithAIOptions(new(planner)) + .WithAIOptions(new(planner) { EnableFeedbackLoop = true }) .WithStorage(sp.GetService()!) .Build(); app.AI.ImportActions(new ActionHandlers()); + app.OnFeedbackLoop((turnContext, turnState, feedbackLoopData, _) => + { + Console.WriteLine("Feedback loop triggered"); + return Task.CompletedTask; + }); + return app; }); From ca9b71cab45874683444d863437559de5a8d89fe Mon Sep 17 00:00:00 2001 From: Lily Du Date: Wed, 30 Oct 2024 09:14:30 -0700 Subject: [PATCH 05/18] [C#] feat: Streaming - Citations & Sensitivity Label (#2147) ## Linked issues closes: #1970 ## Details This will work slightly differently than the previous non-streaming Plan flow. Citations and its respective sensitivity labels are added per each text chunk queued. However, these will only be rendered in the final message (when the full message has been received). Rather than exposing the SensitivityUsageInfo object as an override on the PredictedSayCommand, the label can now be directly set as usageInfo in the AIEntity object along with the AIGenerated label and the citations. **screenshots**: ![image](https://github.com/user-attachments/assets/81c0f3dd-f958-4b4b-8cbf-56bf36f24f78) ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --------- Co-authored-by: lilydu --- .../Application/StreamingResponseTests.cs | 11 +++- .../Microsoft.TeamsAI/AI/Action/AIEntity.cs | 6 ++ .../Microsoft.TeamsAI/AI/Clients/LLMClient.cs | 5 +- .../Application/StreamingResponse.cs | 59 ++++++++++++++++++- 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs index 5e65180ed..d75139e13 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/StreamingResponseTests.cs @@ -2,6 +2,8 @@ using AdaptiveCards; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; +using Microsoft.Teams.AI.AI.Action; +using Microsoft.Teams.AI.AI.Models; using Microsoft.Teams.AI.Application; using Microsoft.Teams.AI.Exceptions; using Microsoft.Teams.AI.Tests.TestUtils; @@ -216,12 +218,15 @@ void CaptureSend(Activity[] arg) from: new() { Id = "fromId" } )); StreamingResponse streamer = new(turnContext); - streamer.QueueTextChunk("first"); + List citations = new List(); + citations.Add(new Citation(content: "test-content", title: "test", url: "https://example.com")); + streamer.QueueTextChunk("first", citations); await streamer.WaitForQueue(); streamer.QueueTextChunk("second"); await streamer.WaitForQueue(); streamer.EnableFeedbackLoop = true; streamer.EnableGeneratedByAILabel = true; + streamer.SensitivityLabel = new SensitivityUsageInfo() { Name= "Sensitivity"}; await streamer.EndStream(); Assert.Equal(2, streamer.UpdatesSent()); } @@ -263,6 +268,10 @@ void CaptureSend(Activity[] arg) await streamer.EndStream(); Assert.Equal(2, streamer.UpdatesSent()); Assert.Single(streamer.Attachments); + if (streamer.Citations != null) + { + Assert.Empty(streamer.Citations); + } } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs index e0280b45b..79ebe7139 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Action/AIEntity.cs @@ -43,6 +43,12 @@ public class AIEntity : Entity /// [JsonProperty(PropertyName = "citation")] public List Citation { get; set; } = new(); + + /// + /// Optional sensitivity content information. + /// + [JsonProperty(PropertyName = "usageInfo")] + public SensitivityUsageInfo? UsageInfo { get; set; } } /// diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs index 3eb6c52d3..bee3b26ab 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -197,9 +197,12 @@ public async Task CompletePromptAsync( // Send chunk to client string text = args.Chunk.delta?.GetContent() ?? ""; + IList? citations = args.Chunk.delta?.Context?.Citations ?? null; + + if (text.Length > 0) { - streamer.QueueTextChunk(text); + streamer.QueueTextChunk(text, citations); } }); diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs index fb844c5c5..61821abbe 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs @@ -1,8 +1,11 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; using Microsoft.Teams.AI.AI.Action; +using Microsoft.Teams.AI.AI.Models; using Microsoft.Teams.AI.Exceptions; +using Microsoft.Teams.AI.Utilities; using Newtonsoft.Json.Linq; +using System; namespace Microsoft.Teams.AI.Application { @@ -44,6 +47,16 @@ public class StreamingResponse /// public bool? EnableGeneratedByAILabel { get; set; } = false; + /// + /// The citations for the response. + /// + public List? Citations { get; set; } = new(); + + /// + /// The sensitivity label for the response. + /// + public SensitivityUsageInfo? SensitivityLabel { get; set; } + /// /// Gets the stream ID of the current response. /// Assigned after the initial update is sent. @@ -107,8 +120,9 @@ public void QueueInformativeUpdate(string text) /// 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) + public void QueueTextChunk(string text, IList? citations = null) { if (this._ended) { @@ -116,6 +130,40 @@ public void QueueTextChunk(string text) } 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(); } @@ -283,7 +331,14 @@ private async Task SendActivity(Activity activity) // Add in Generated by AI if (this.EnableGeneratedByAILabel == true) { - activity.Entities.Add(new AIEntity()); + AIEntity entity = new AIEntity(); + if (this.Citations != null && this.Citations.Count > 0) + { + entity.Citation = this.Citations; + } + + entity.UsageInfo = this.SensitivityLabel; + activity.Entities.Add(entity); } } From 92d4aef3b8ed4c37ac232564ddc67121cb16e7d0 Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:01:18 -0700 Subject: [PATCH 06/18] [C#] fix: Map tokens config to `max_tokens` when non-o1 model is used. (#2150) ## Linked issues closes: #minor ## Details The `OpenAI` sdk automatically maps the `max_tokens` configuration to the `MaxOutputTokenCount` property (i.e `max_completion_tokens`). However this does not work with Azure OpenAI service as it expects `max_tokens` for non-o1 models. #### Change details * In `OpenAIModel.cs` if the model is not in the o1 series, then use `max_tokens` field by default. ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../AITests/Models/OpenAIModelTests.cs | 19 +++++++++++++++++++ .../AI/Models/OpenAIModel.cs | 19 +++++++++++++++++++ .../samples/04.e.twentyQuestions/teamsapp.yml | 1 + .../08.datasource.azureopenai/teamsapp.yml | 1 + 4 files changed, 40 insertions(+) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs index 2b5b98f27..93e63eeda 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/OpenAIModelTests.cs @@ -33,6 +33,25 @@ public void Test_Constructor_OpenAI() new OpenAIModel(options); } + [Fact] + public void Test_SetMaxTokens() + { + // Arrange + var options = new OpenAIModelOptions("test-key", "test-model"); + var chatCompletionOptions = new ChatCompletionOptions(); + var model = new OpenAIModel(options); + var testTokens = 100; + + // Act + model.SetMaxTokens(testTokens, chatCompletionOptions); + + // Assert + MethodInfo info = chatCompletionOptions.GetType().GetMethod("get__deprecatedMaxTokens", BindingFlags.NonPublic | BindingFlags.Instance)!; + int maxTokens = (int)info.Invoke(chatCompletionOptions, null)!; + Assert.Equal(testTokens, maxTokens); + } + + [Fact] public void Test_Constructor_AzureOpenAI_InvalidAzureApiVersion() { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs index be7274c9d..8fa533e15 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs @@ -19,7 +19,10 @@ using Azure.AI.OpenAI.Chat; using OpenAI.Chat; using Microsoft.Teams.AI.Application; +using System.Reflection; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.Teams.AI.Tests")] #pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. namespace Microsoft.Teams.AI.AI.Models { @@ -223,6 +226,10 @@ public async Task CompletePromptAsync(ITurnContext turnContext, chatCompletionOptions.Temperature = 1; chatCompletionOptions.TopP = 1; chatCompletionOptions.PresencePenalty = 0; + } else + { + // `MaxOutputTokenCount` is not supported for non-o1 Azure OpenAI models, hence it needs to be set for it to work. + SetMaxTokens(completion.MaxTokens, chatCompletionOptions); } // Set tools configurations @@ -248,6 +255,12 @@ public async Task CompletePromptAsync(ITurnContext turnContext, AddAzureChatExtensionConfigurations(chatCompletionOptions, additionalData); } + if (_options.LogRequests!.Value) + { + _logger.LogTrace("CHAT COMPLETION CONFIG:"); + _logger.LogTrace(JsonSerializer.Serialize(chatCompletionOptions, _serializerOptions)); + } + PipelineResponse? rawResponse = null; ClientResult? chatCompletionsResponse = null; @@ -458,6 +471,12 @@ private void AddAzureChatExtensionConfigurations(ChatCompletionOptions options, } } } + + internal void SetMaxTokens(int maxTokens, ChatCompletionOptions options) + { + MethodInfo setMaxTokens = options.GetType().GetMethod("set__deprecatedMaxTokens", BindingFlags.NonPublic | BindingFlags.Instance); + setMaxTokens.Invoke(options, new object[] { maxTokens }); + } } } #pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/dotnet/samples/04.e.twentyQuestions/teamsapp.yml b/dotnet/samples/04.e.twentyQuestions/teamsapp.yml index 59234e804..a535fdee3 100644 --- a/dotnet/samples/04.e.twentyQuestions/teamsapp.yml +++ b/dotnet/samples/04.e.twentyQuestions/teamsapp.yml @@ -81,3 +81,4 @@ deploy: with: artifactFolder: bin/Release/net6.0/win-x86/publish resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: 6d2b99e4-1480-4218-8d1c-46630228f713 diff --git a/dotnet/samples/08.datasource.azureopenai/teamsapp.yml b/dotnet/samples/08.datasource.azureopenai/teamsapp.yml index 09e8c1e3c..7d7b99ae6 100644 --- a/dotnet/samples/08.datasource.azureopenai/teamsapp.yml +++ b/dotnet/samples/08.datasource.azureopenai/teamsapp.yml @@ -95,3 +95,4 @@ deploy: # You can replace it with an existing Azure Resource ID or other # custom environment variable. resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: 8c3187b5-ec34-4e3b-afd8-4526ab52d06e From 437dbbe253fd6ada5f40a5ee4a8a659353bb441c Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:34:53 -0700 Subject: [PATCH 07/18] [C#] bump: dotnet to 1.8.0 (#2157) ## Linked issues closes: #minor ## Details * Bump `Microsoft.Teams.AI` to version 1.8.0 * Update all the samples to point to this version. ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../Microsoft.TeamsAI/Microsoft.Teams.AI.csproj | 7 +------ dotnet/samples/01.messaging.echoBot/EchoBot.csproj | 2 +- .../SearchCommand.csproj | 2 +- .../03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj | 2 +- dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj | 2 +- dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj | 2 +- .../samples/04.ai.c.actionMapping.lightBot/LightBot.csproj | 2 +- .../samples/04.ai.d.chainedActions.listBot/ListBot.csproj | 2 +- .../04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj | 2 +- dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj | 2 +- .../04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj | 2 +- dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj | 2 +- dotnet/samples/05.chatModeration/ChatModeration.csproj | 2 +- dotnet/samples/06.assistants.a.mathBot/MathBot.csproj | 2 +- dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj | 2 +- dotnet/samples/06.auth.oauth.bot/BotAuth.csproj | 2 +- .../MessageExtensionAuth.csproj | 2 +- dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj | 2 +- .../MessageExtensionAuth.csproj | 2 +- .../AzureAISearchBot/AzureAISearchBot.csproj | 2 +- .../08.datasource.azureopenai/AzureOpenAIBot.csproj | 2 +- 21 files changed, 21 insertions(+), 26 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index f2c73fe7b..741f08c87 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -7,7 +7,7 @@ enable Microsoft.Teams.AI Microsoft Teams AI SDK - 1.7.0 + 1.8.0 Microsoft Microsoft © Microsoft Corporation. All rights reserved. @@ -52,11 +52,6 @@ - - - - - diff --git a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj index d83fdff2f..e41f28dc3 100644 --- a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj +++ b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj @@ -17,7 +17,7 @@ - + diff --git a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj index 33a856cd4..c6af635c8 100644 --- a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj +++ b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj index 1ab4bdc47..5292b9795 100644 --- a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj +++ b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj @@ -13,7 +13,7 @@ - + diff --git a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj index 5f4d8076e..9ce3c4a91 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj index c1166c405..d2efcaea5 100644 --- a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj +++ b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj @@ -15,7 +15,7 @@ - + diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj index ca4a0ca5a..1fcd96b55 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj index 32b7c2558..4503d835c 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj @@ -12,7 +12,7 @@ - + diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj index 837189142..9a92bcaa4 100644 --- a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj +++ b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj index fdc5435aa..05a7a3cb3 100644 --- a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj +++ b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj index 5f8fa3c35..00a762075 100644 --- a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj index ce0215307..33faa98f3 100644 --- a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj +++ b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj @@ -13,7 +13,7 @@ - + diff --git a/dotnet/samples/05.chatModeration/ChatModeration.csproj b/dotnet/samples/05.chatModeration/ChatModeration.csproj index e5fc30c1d..fe4bf3898 100644 --- a/dotnet/samples/05.chatModeration/ChatModeration.csproj +++ b/dotnet/samples/05.chatModeration/ChatModeration.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj index 30d0fc8d3..3bad03104 100644 --- a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj +++ b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj index bb37ee383..b33d917d4 100644 --- a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj +++ b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj @@ -17,7 +17,7 @@ - + diff --git a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj index 345795d3f..6b080750f 100644 --- a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj index 70aea86e1..e06ba9bf8 100644 --- a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj index ce59cbd46..86e26b01c 100644 --- a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj index c08cdef1e..26cb4a83e 100644 --- a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj index ad339e29f..1ea7d35da 100644 --- a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj +++ b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj @@ -15,7 +15,7 @@ - + diff --git a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj index c847f2400..fbc8b66b1 100644 --- a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj +++ b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj @@ -15,7 +15,7 @@ - + From 9bcd35f11203b85e37a5bd4122d5edbf22bb552b Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:59:48 -0700 Subject: [PATCH 08/18] Update manifest.json --- dotnet/samples/05.chatModeration/appPackage/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/05.chatModeration/appPackage/manifest.json b/dotnet/samples/05.chatModeration/appPackage/manifest.json index 81f3ef407..742c92871 100644 --- a/dotnet/samples/05.chatModeration/appPackage/manifest.json +++ b/dotnet/samples/05.chatModeration/appPackage/manifest.json @@ -16,8 +16,8 @@ "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" }, "description": { - "short": "Sample bot that thinks it's a Chef to help you cook Teams apps", - "full": "Sample bot that thinks it's a Chef to help you cook Teams apps" + "short": "Sample bot shows how to incorporate Content Safety control.", + "full": "Sample bot shows how to incorporate Content Safety control." }, "icons": { "outline": "outline.png", From 804ff053f7f3a2271a17c333461debfc8d53bf3b Mon Sep 17 00:00:00 2001 From: Lily Du Date: Mon, 11 Nov 2024 14:29:26 -0800 Subject: [PATCH 09/18] [C#] fix: update streamType enum conversion to string (#2178) ## Linked issues closes: #minor ## Details for `entities` object, update `streamType` enum conversion to string instead of int ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../Microsoft.TeamsAI/Application/StreamingResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs index 61821abbe..34d061d85 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/StreamingResponse.cs @@ -313,7 +313,7 @@ private async Task SendActivity(Activity activity) { Properties = JObject.FromObject(new { streamId = ((StreamingChannelData) activity.ChannelData).streamId, - streamType = ((StreamingChannelData) activity.ChannelData).StreamType, + streamType = ((StreamingChannelData) activity.ChannelData).StreamType.ToString(), streamSequence = ((StreamingChannelData) activity.ChannelData).StreamSequence, }) From 2c651840c3b59ab495ebd3bcd6a06766d40e394c Mon Sep 17 00:00:00 2001 From: Kavin Date: Tue, 12 Nov 2024 08:26:44 -0800 Subject: [PATCH 10/18] bump to v1.8.1 --- .../Microsoft.TeamsAI/Microsoft.Teams.AI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index c8a2437ef..5550baf0c 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -7,7 +7,7 @@ enable Microsoft.Teams.AI Microsoft Teams AI SDK - 1.8.0 + 1.8.1 Microsoft Microsoft © Microsoft Corporation. All rights reserved. From 1c7d9c997b552f6c034fd5b119322bcacd93436b Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:56:58 -0800 Subject: [PATCH 11/18] [C#] fix: Upgrade tokenizers library to the GA version 1.0.0 (#2210) #minor --- .../Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs | 2 +- .../Microsoft.TeamsAI/Microsoft.Teams.AI.csproj | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs index 72b6e52cd..ab140f8b8 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Tokenizers/GPTTokenizer.cs @@ -24,7 +24,7 @@ public class GPTTokenizer : ITokenizer /// Creates an instance of `GPTTokenizer` /// /// model to encode/decode for - public GPTTokenizer(string model) => this._encoding = TiktokenTokenizer.CreateForModel("gpt-4"); + public GPTTokenizer(string model) => this._encoding = TiktokenTokenizer.CreateForModel(model); /// /// Encode diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index 5550baf0c..3025fc921 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -45,9 +45,10 @@ - + + - + From 2ba12e390074f6e3e7d0a39101ca0e354f02daf4 Mon Sep 17 00:00:00 2001 From: Steven Ickman Date: Mon, 9 Dec 2024 09:13:45 -0800 Subject: [PATCH 12/18] [C#] feat: Implemented basic changes needed to support streaming tool calls (#2214) ## Linked issues closes: #2098 ## Details Port the streaming support for tools augmentation to C#. #### Change details - Added StreamingChatToolCallsBuilder and SequenceBuilder helper classes. - Updated OpenAIModel class to process tool call chunks and return them as a message with ActionCalls. - Updated the LLMClient class to pass through the ActionCalls without closing the streaming response session. **code snippets**: **screenshots**: ## Attestation Checklist - [ ] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes ### Additional information > Feel free to add other relevant information below --------- Signed-off-by: dependabot[bot] Co-authored-by: kavin <115390646+singhk97@users.noreply.github.com> Co-authored-by: Lily Du Co-authored-by: lilydu Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Corina <14900841+corinagum@users.noreply.github.com> Co-authored-by: Yiqing Zhao Co-authored-by: Yiqing Zhao Co-authored-by: Alex Acebo --- .github/CODEOWNERS | 6 +- .github/workflows/coverage.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/dotnet-build-test-lint.yml | 4 +- .github/workflows/dotnet-codeql.yml | 4 +- .github/workflows/js-build-test-lint.yml | 2 +- .github/workflows/js-codeql.yml | 4 +- .github/workflows/python-build-test-lint.yml | 2 +- .github/workflows/python-codeql.yml | 4 +- .github/workflows/scorecards.yml | 2 +- .../Microsoft.TeamsAI/AI/Clients/LLMClient.cs | 78 +- .../AI/Models/OpenAIModel.cs | 32 +- .../AI/Models/SequenceBuilder.cs | 65 + .../Models/StreamingChatToolCallsBuilder.cs | 59 + .../LightBotActions.cs | 13 +- .../04.ai.c.actionMapping.lightBot/Program.cs | 6 +- js/package.json | 9 +- js/packages/teams-ai/package.json | 10 +- js/packages/teams-ai/src/AI.ts | 19 +- js/packages/teams-ai/src/Application.spec.ts | 308 +++- js/packages/teams-ai/src/Application.ts | 171 +- js/packages/teams-ai/src/Messages.spec.ts | 56 + js/packages/teams-ai/src/Messages.ts | 100 ++ js/packages/teams-ai/src/StreamingResponse.ts | 2 +- js/packages/teams-ai/src/Utilities.spec.ts | 12 +- js/packages/teams-ai/src/actions/DoCommand.ts | 1 + .../teams-ai/src/actions/FlaggedInput.ts | 1 + .../teams-ai/src/actions/FlaggedOutput.ts | 3 +- js/packages/teams-ai/src/actions/HttpError.ts | 1 + js/packages/teams-ai/src/actions/PlanReady.ts | 1 + .../teams-ai/src/actions/SayCommand.spec.ts | 2 +- .../teams-ai/src/actions/SayCommand.ts | 16 +- .../teams-ai/src/actions/TooManySteps.ts | 1 + js/packages/teams-ai/src/actions/Unknown.ts | 1 + .../augmentations/ToolsAugmentation.spec.ts | 42 + .../src/augmentations/ToolsAugmentation.ts | 20 +- .../src/authentication/Authentication.ts | 20 +- .../src/planners/AssistantsPlanner.spec.ts | 789 ++++----- .../src/planners/AssistantsPlanner.ts | 411 +++-- .../teams-ai/src/types/ClientCitation.ts | 49 +- .../f.chatModeration/src/bot.ts | 2 +- .../04.ai-apps/a.teamsChefBot/package.json | 2 +- .../e.assistants-orderBot/.gitignore | 3 + .../e.assistants-orderBot/.vscode/launch.json | 28 +- .../e.assistants-orderBot/.vscode/tasks.json | 109 +- .../devTools/teamsapptester | 1 + .../e.assistants-orderBot/env/.env.testtool | 3 + .../e.assistants-orderBot/package.json | 8 +- .../e.assistants-orderBot/src/bot.ts | 20 +- .../e.assistants-orderBot/src/index.ts | 2 +- .../teamsapp.testtool.yml | 27 + .../e.assistants-orderBot/teamsapp.yml | 163 +- js/samples/04.ai-apps/f.whoBot/package.json | 2 +- .../h.datasource-azureOpenAI/src/index.ts | 2 +- .../i.teamsChefBot-streaming/package.json | 2 +- .../i.teamsChefBot-streaming/src/index.ts | 8 +- .../a.oauth-adaptiveCard/package.json | 2 +- .../b.oauth-bot/package.json | 2 +- .../c.oauth-messageExtension/package.json | 2 +- .../d.teamsSSO-bot/package.json | 2 +- .../e.teamsSSO-messageExtension/package.json | 2 +- js/yarn.lock | 1415 +++++++++-------- .../azure_content_safety_moderator.py | 67 +- .../test_azure_content_safety_moderator.py | 18 +- python/samples/05.chatModeration/src/bot.py | 6 +- .../src/prompts/chat/config.json | 2 +- 66 files changed, 2537 insertions(+), 1693 deletions(-) create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs create mode 100644 dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs create mode 100644 js/packages/teams-ai/src/Messages.spec.ts create mode 100644 js/packages/teams-ai/src/Messages.ts create mode 120000 js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester create mode 100644 js/samples/04.ai-apps/e.assistants-orderBot/env/.env.testtool create mode 100644 js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.testtool.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f792cf7e4..e4fd95f41 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,15 +2,15 @@ # JS -/js @aacebo @corinagum @lilyydu @singhk97 +/js @aacebo @corinagum @lilyydu @singhk97 @rajan-chari # .NET -/dotnet @aacebo @corinagum @lilyydu @singhk97 +/dotnet @aacebo @corinagum @lilyydu @singhk97 @rajan-chari # Python -/python @aacebo @corinagum @lilyydu @singhk97 +/python @aacebo @corinagum @lilyydu @singhk97 @rajan-chari # TTK diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7baee3d43..c67f92e4a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -71,7 +71,7 @@ jobs: - name: Test run: dotnet test Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj --verbosity normal --logger trx --results-directory ./TestResults --collect:"XPlat Code Coverage" --configuration Release - name: Coverage - uses: danielpalme/ReportGenerator-GitHub-Action@62f9e70ab348d56eee76d446b4db903a85ab0ea8 # 5.3.11 + uses: danielpalme/ReportGenerator-GitHub-Action@810356ce07a94200154301fb73d878e327b2dd58 # 5.4.1 with: reports: ${{ env.SOLUTION_DIR }}TestResults/*/coverage.cobertura.xml targetdir: ${{ env.SOLUTION_DIR }}TestResults/coverage diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 74eee94fb..fea1d11c9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 + uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 diff --git a/.github/workflows/dotnet-build-test-lint.yml b/.github/workflows/dotnet-build-test-lint.yml index 4f255d75e..159c10f0e 100644 --- a/.github/workflows/dotnet-build-test-lint.yml +++ b/.github/workflows/dotnet-build-test-lint.yml @@ -43,7 +43,7 @@ jobs: - name: Test run: dotnet test Microsoft.TeamsAI.Tests/Microsoft.Teams.AI.Tests.csproj --no-restore --verbosity normal --logger trx --results-directory ./TestResults --collect:"XPlat Code Coverage" --configuration Release - name: Coverage - uses: danielpalme/ReportGenerator-GitHub-Action@62f9e70ab348d56eee76d446b4db903a85ab0ea8 # 5.3.11 + uses: danielpalme/ReportGenerator-GitHub-Action@810356ce07a94200154301fb73d878e327b2dd58 # 5.4.1 with: reports: ${{ env.SOLUTION_DIR }}TestResults/*/coverage.cobertura.xml targetdir: ${{ env.SOLUTION_DIR }}TestResults/coverage @@ -55,6 +55,6 @@ jobs: name: testresults-dotnet-${{ matrix.dotnet-version }} path: ${{ env.SOLUTION_DIR }}TestResults - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit diff --git a/.github/workflows/dotnet-codeql.yml b/.github/workflows/dotnet-codeql.yml index 7946effad..6e91ff779 100644 --- a/.github/workflows/dotnet-codeql.yml +++ b/.github/workflows/dotnet-codeql.yml @@ -39,7 +39,7 @@ jobs: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: languages: csharp - name: Setup .NET @@ -50,6 +50,6 @@ jobs: working-directory: dotnet/packages/Microsoft.TeamsAI/ run: dotnet build Microsoft.Teams.AI.sln --configuration Release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: category: "/language:csharp" diff --git a/.github/workflows/js-build-test-lint.yml b/.github/workflows/js-build-test-lint.yml index 2b16fa133..fcddf48ac 100644 --- a/.github/workflows/js-build-test-lint.yml +++ b/.github/workflows/js-build-test-lint.yml @@ -42,6 +42,6 @@ jobs: - name: Lint run: yarn lint - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit diff --git a/.github/workflows/js-codeql.yml b/.github/workflows/js-codeql.yml index 1c0541e4b..768201763 100644 --- a/.github/workflows/js-codeql.yml +++ b/.github/workflows/js-codeql.yml @@ -38,10 +38,10 @@ jobs: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: languages: javascript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: category: "/language:javascript" diff --git a/.github/workflows/python-build-test-lint.yml b/.github/workflows/python-build-test-lint.yml index ab934a4db..e390e26c4 100644 --- a/.github/workflows/python-build-test-lint.yml +++ b/.github/workflows/python-build-test-lint.yml @@ -51,6 +51,6 @@ jobs: run: | python scripts/lint.py - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit diff --git a/.github/workflows/python-codeql.yml b/.github/workflows/python-codeql.yml index 57a3a71ec..f27a1fd69 100644 --- a/.github/workflows/python-codeql.yml +++ b/.github/workflows/python-codeql.yml @@ -38,10 +38,10 @@ jobs: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: category: "/language:python" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 9d5b231b8..9fafeb0dc 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -66,6 +66,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: sarif_file: results.sarif diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs index bee3b26ab..3b07e4a9d 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Clients/LLMClient.cs @@ -1,4 +1,5 @@ -using Microsoft.Bot.Builder; +using Google.Protobuf.WellKnownTypes; +using Microsoft.Bot.Builder; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Teams.AI.AI.Models; @@ -155,7 +156,6 @@ public async Task CompletePromptAsync( ) { // Define event handlers - bool isStreaming = false; StreamingResponse? streamer = null; BeforeCompletionHandler handleBeforeCompletion = new((object sender, BeforeCompletionEventArgs args) => @@ -168,22 +168,26 @@ public async Task CompletePromptAsync( if (args.Streaming) { - isStreaming = true; - - // Create streamer and send initial message - streamer = new StreamingResponse(context); - memory.SetValue("temp.streamer", streamer); - - if (this._enableFeedbackLoop != null) + // Attach to any existing streamer + // - see tool call note below to understand. + streamer = (StreamingResponse?)memory.GetValue("temp.streamer"); + if (streamer == null) { - streamer.EnableFeedbackLoop = this._enableFeedbackLoop; - } + // Create streamer and send initial message + streamer = new StreamingResponse(context); + memory.SetValue("temp.streamer", streamer); - streamer.EnableGeneratedByAILabel = true; + if (this._enableFeedbackLoop != null) + { + streamer.EnableFeedbackLoop = this._enableFeedbackLoop; + } - if (!string.IsNullOrEmpty(this._startStreamingMessage)) - { - streamer.QueueInformativeUpdate(this._startStreamingMessage!); + streamer.EnableGeneratedByAILabel = true; + + if (!string.IsNullOrEmpty(this._startStreamingMessage)) + { + streamer.QueueInformativeUpdate(this._startStreamingMessage!); + } } } }); @@ -195,6 +199,15 @@ public async Task CompletePromptAsync( return; } + + // Ignore content without text + // - The chunk is likely for a Tool Call. + // - See the tool call note below to understand why we're ignoring them. + if (args.Chunk.delta?.GetContent() == null) + { + return; + } + // Send chunk to client string text = args.Chunk.delta?.GetContent() ?? ""; IList? citations = args.Chunk.delta?.Context?.Citations ?? null; @@ -234,23 +247,32 @@ public async Task CompletePromptAsync( cancellationToken ); - if (response.Status != PromptResponseStatus.Success) - { - return response; - } - else + // Handle streaming responses + if (streamer != null) { - if (isStreaming) + // Tool call handling + // - We need to keep the streamer around during tool calls so we're just letting them return as normal + // messages minus the message content. The text content is being streamed to the client in chunks. + // - When the tool call completes we'll call back into ActionPlanner and end up re-attaching to the + // streamer. This will result in us continuing to stream the response to the client. + if (response.Message?.ActionCalls != null && response.Message.ActionCalls.Count > 0) { - // Delete the message from the response to avoid sending it twice. - response.Message = null; + // Ensure content is empty for tool calls + response.Message.Content = ""; } - } + else + { + if (response.Status == PromptResponseStatus.Success) + { + // Delete message from response to avoid sending it twice + response.Message = null; + } - // End the stream - if (streamer != null) - { - await streamer.EndStream(); + // End the stream and remove pointer from memory + // - We're not listening for the response received event because we can't await the completion of events. + await streamer.EndStream(); + memory.DeleteValue("temp.streamer"); + } } // Get input message/s diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs index 8fa533e15..be0723385 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/OpenAIModel.cs @@ -21,6 +21,7 @@ using Microsoft.Teams.AI.Application; using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.Extensions.Options; [assembly: InternalsVisibleTo("Microsoft.Teams.AI.Tests")] #pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -214,7 +215,6 @@ public async Task CompletePromptAsync(ITurnContext turnContext, ChatCompletionOptions chatCompletionOptions = new() { - MaxOutputTokenCount = completion.MaxTokens, Temperature = (float)completion.Temperature, TopP = (float)completion.TopP, PresencePenalty = (float)completion.PresencePenalty, @@ -223,6 +223,7 @@ public async Task CompletePromptAsync(ITurnContext turnContext, if (isO1Model) { + chatCompletionOptions.MaxOutputTokenCount = completion.MaxTokens; chatCompletionOptions.Temperature = 1; chatCompletionOptions.TopP = 1; chatCompletionOptions.PresencePenalty = 0; @@ -282,6 +283,7 @@ public async Task CompletePromptAsync(ITurnContext turnContext, }; AsyncCollectionResult streamCompletion = _openAIClient.GetChatClient(_deploymentName).CompleteChatStreamingAsync(chatMessages, chatCompletionOptions, cancellationToken); + var toolCallBuilder = new StreamingChatToolCallsBuilder(); await foreach (StreamingChatCompletionUpdate delta in streamCompletion) { if (delta.Role != null) @@ -295,9 +297,19 @@ public async Task CompletePromptAsync(ITurnContext turnContext, message.Content += delta.ContentUpdate[0].Text; } - // TODO: Handle tool calls + // Handle tool calls + if (isToolsAugmentation && delta.ToolCallUpdates != null && delta.ToolCallUpdates.Count > 0) + { + foreach (var toolCallUpdate in delta.ToolCallUpdates) + { + toolCallBuilder.Append(toolCallUpdate); + } + } - ChatMessage currDeltaMessage = new(delta); + ChatMessage currDeltaMessage = new(delta) + { + ActionCalls = message.ActionCalls // Ensure ActionCalls are included + }; PromptChunk chunk = new() { delta = currDeltaMessage @@ -311,7 +323,19 @@ public async Task CompletePromptAsync(ITurnContext turnContext, _logger.LogTrace("CHUNK", delta); } - Events!.OnChunkReceived(args); + Events!.OnChunkReceived(args); + } + + // Add any tool calls to message + var toolCalls = toolCallBuilder.Build(); + if (toolCalls.Count > 0) + { + message.ActionCalls = new List(); + foreach (var toolCall in toolCalls) + { + var actionCall = new ActionCall(toolCall); + message.ActionCalls.Add(actionCall); + } } promptResponse.Message = message; diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs new file mode 100644 index 000000000..17105b53a --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs @@ -0,0 +1,65 @@ +using System.Buffers; +using System.Diagnostics; + +namespace Microsoft.Teams.AI.AI.Models +{ + public class SequenceBuilder + { + private Segment _first; + private Segment _last; + + public void Append(ReadOnlyMemory data) + { + if (_first == null) + { + Debug.Assert(_last == null); + _first = new Segment(data); + _last = _first; + } + else + { + _last = _last!.Append(data); + } + } + + public ReadOnlySequence Build() + { + if (_first == null) + { + Debug.Assert(_last == null); + return ReadOnlySequence.Empty; + } + + if (_first == _last) + { + Debug.Assert(_first.Next == null); + return new ReadOnlySequence(_first.Memory); + } + + return new ReadOnlySequence(_first, 0, _last!, _last!.Memory.Length); + } + + private sealed class Segment : ReadOnlySequenceSegment + { + public Segment(ReadOnlyMemory items) : this(items, 0) + { + } + + private Segment(ReadOnlyMemory items, long runningIndex) + { + Debug.Assert(runningIndex >= 0); + Memory = items; + RunningIndex = runningIndex; + } + + public Segment Append(ReadOnlyMemory items) + { + long runningIndex; + checked { runningIndex = RunningIndex + Memory.Length; } + Segment segment = new(items, runningIndex); + Next = segment; + return segment; + } + } + } +} diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs new file mode 100644 index 000000000..80f10c10f --- /dev/null +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs @@ -0,0 +1,59 @@ +using OpenAI.Chat; +using System.Buffers; + +namespace Microsoft.Teams.AI.AI.Models +{ + public class StreamingChatToolCallsBuilder + { + private readonly Dictionary _indexToToolCallId = []; + private readonly Dictionary _indexToFunctionName = []; + private readonly Dictionary> _indexToFunctionArguments = []; + + public void Append(StreamingChatToolCallUpdate toolCallUpdate) + { + // Keep track of which tool call ID belongs to this update index. + if (toolCallUpdate.ToolCallId != null) + { + _indexToToolCallId[toolCallUpdate.Index] = toolCallUpdate.ToolCallId; + } + + // Keep track of which function name belongs to this update index. + if (toolCallUpdate.FunctionName != null) + { + _indexToFunctionName[toolCallUpdate.Index] = toolCallUpdate.FunctionName; + } + + // Keep track of which function arguments belong to this update index, + // and accumulate the arguments as new updates arrive. + if (toolCallUpdate.FunctionArgumentsUpdate != null && !toolCallUpdate.FunctionArgumentsUpdate.ToMemory().IsEmpty) + { + if (!_indexToFunctionArguments.TryGetValue(toolCallUpdate.Index, out SequenceBuilder argumentsBuilder)) + { + argumentsBuilder = new SequenceBuilder(); + _indexToFunctionArguments[toolCallUpdate.Index] = argumentsBuilder; + } + + argumentsBuilder.Append(toolCallUpdate.FunctionArgumentsUpdate); + } + } + + public IReadOnlyList Build() + { + List toolCalls = []; + + foreach (KeyValuePair indexToToolCallIdPair in _indexToToolCallId) + { + ReadOnlySequence sequence = _indexToFunctionArguments[indexToToolCallIdPair.Key].Build(); + + ChatToolCall toolCall = ChatToolCall.CreateFunctionToolCall( + id: indexToToolCallIdPair.Value, + functionName: _indexToFunctionName[indexToToolCallIdPair.Key], + functionArguments: BinaryData.FromBytes(sequence.ToArray())); + + toolCalls.Add(toolCall); + } + + return toolCalls; + } + } +} diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs index 19f9cd42f..a34a842b0 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBotActions.cs @@ -2,6 +2,9 @@ using Microsoft.Bot.Builder; using Microsoft.Teams.AI.AI.Action; using Microsoft.Teams.AI.AI; +using System.Diagnostics; +using System.Threading; +using Microsoft.Teams.AI.State; namespace LightBot { @@ -11,7 +14,7 @@ public class LightBotActions public async Task LightsOn([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState) { turnState.Conversation.LightsOn = true; - await turnContext.SendActivityAsync(MessageFactory.Text("[lights on]")); + Trace.WriteLine("[The lights are on]"); return "the lights are now on"; } @@ -19,7 +22,7 @@ public async Task LightsOn([ActionTurnContext] ITurnContext turnContext, public async Task LightsOff([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState) { turnState.Conversation.LightsOn = false; - await turnContext.SendActivityAsync(MessageFactory.Text("[lights off]")); + Trace.WriteLine("[The lights are off]"); return "the lights are now off"; } @@ -33,7 +36,7 @@ public async Task LightsOff([ActionTurnContext] ITurnContext turnContext if (time != null && time is long timeLong) { int timeInt = (int)timeLong; - await turnContext.SendActivityAsync(MessageFactory.Text($"[pausing for {timeInt / 1000} seconds]")); + Trace.WriteLine($"[pausing for {timeInt / 1000} seconds]"); await Task.Delay(timeInt); } } @@ -44,14 +47,14 @@ public async Task LightsOff([ActionTurnContext] ITurnContext turnContext [Action("LightStatus")] public async Task LightStatus([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState) { - await turnContext.SendActivityAsync(ResponseGenerator.LightStatus(turnState.Conversation.LightsOn)); + Trace.WriteLine(ResponseGenerator.LightStatus(turnState.Conversation.LightsOn)); return turnState.Conversation.LightsOn ? "the lights are on" : "the lights are off"; } [Action(AIConstants.UnknownActionName)] public async Task UnknownAction([ActionTurnContext] TurnContext turnContext, [ActionName] string action) { - await turnContext.SendActivityAsync(ResponseGenerator.UnknownAction(action ?? "Unknown")); + Trace.WriteLine(ResponseGenerator.UnknownAction(action ?? "Unknown")); return "unknown action"; } } diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs b/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs index 9fcdd736a..b3d8785fe 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/Program.cs @@ -37,7 +37,8 @@ builder.Services.AddSingleton(sp => new( new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-4o") { - LogRequests = true + LogRequests = true, + Stream = true }, sp.GetService() )); @@ -51,7 +52,8 @@ config.Azure.OpenAIEndpoint ) { - LogRequests = true + LogRequests = true, + Stream = true }, sp.GetService() )); diff --git a/js/package.json b/js/package.json index 6d038d4ed..6b38b07ab 100644 --- a/js/package.json +++ b/js/package.json @@ -29,10 +29,10 @@ "@microsoft/api-extractor": "^7.47.11", "@standardlabs/is-private": "^1.0.1", "@types/jsonwebtoken": "9.0.4", - "@types/lodash": "^4.17.12", + "@types/lodash": "^4.17.13", "@types/mocha": "^10.0.9", "@types/node": "^20.16.1", - "@types/sinon": "^10.0.19", + "@types/sinon": "^17.0.3", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "applicationinsights": "^2.9.6", @@ -47,8 +47,9 @@ "eslint-plugin-prettier": "^5.2.1", "exorcist": "^2.0.0", "express": "^4.21.1", - "mocha-junit-reporter": "^2.0.0", "mocha": "^10.7.3", + "mocha-junit-reporter": "^2.0.0", + "mocha": "^10.8.2", "ms-rest-azure": "^3.0.2", "npm-run-all": "^4.1.5", "nyc": "^15.1.0", @@ -58,7 +59,7 @@ "replace-in-file": "^4.1.0", "rimraf": "^5.0.10", "shx": "^0.3.4", - "sinon": "^16.1.3", + "sinon": "^19.0.2", "source-map-support": "^0.5.19", "sponge": "^0.1.0", "tinyify": "^4.0.0", diff --git a/js/packages/teams-ai/package.json b/js/packages/teams-ai/package.json index 9782cef02..dd87a6da7 100644 --- a/js/packages/teams-ai/package.json +++ b/js/packages/teams-ai/package.json @@ -29,14 +29,13 @@ "openai": "^4.68.2" }, "dependencies": { - "@azure/openai-assistants": "1.0.0-beta.6", - "@azure/msal-node": "^2.15.0", + "@azure/msal-node": "^2.16.1", "axios": "^1.7.5", "botbuilder-dialogs": "^4.23.1", "botframework-connector": "^4.23.1", "botframework-schema": "^4.23.1", "botframework-streaming": "^4.23.1", - "gpt-tokenizer": "^2.5.1", + "gpt-tokenizer": "^2.6.2", "json-colorizer": "^2.2.2", "jsonschema": "1.4.1", "uuid": "^9.0.1", @@ -56,7 +55,7 @@ "eslint": "^8.57.1", "express": "^4.21.1", "jsonwebtoken": "^9.0.2", - "mocha": "10.7.3", + "mocha": "10.8.2", "nyc": "^15.1.0", "rimraf": "5.0.10", "shx": "^0.3.4", @@ -72,7 +71,8 @@ "test": "npx mocha -r ts-node/register src/**/*.spec.ts", "test:debug": "ts-mocha src/**/*.spec.ts --inspect-brk", "test:coverage": "nyc --reporter=html --reporter=text --reporter=text-summary npm test", - "test:compat": "npx api-extractor run --verbose" + "test:compat": "npx api-extractor run --verbose", + "watch": "tsc -b --watch" }, "files": [ "_ts3.4", diff --git a/js/packages/teams-ai/src/AI.ts b/js/packages/teams-ai/src/AI.ts index d04bb3682..a8244e4ce 100644 --- a/js/packages/teams-ai/src/AI.ts +++ b/js/packages/teams-ai/src/AI.ts @@ -68,6 +68,12 @@ export interface AIOptions { * https://github.com/microsoft/teams-ai/blob/main/getting-started/CONCEPTS/POWERED-BY-AI.md */ enable_feedback_loop?: boolean; + + /** + * Optional. Only used when `enable_feedback_loop` == `true`. When set to `custom` the user will be presented with a text input + * to provide feedback. + */ + feedback_loop_type?: 'default' | 'custom'; } /** @@ -104,6 +110,12 @@ export interface ConfiguredAIOptions { * If true, the AI system will enable the feedback loop in Teams that allows a user to give thumbs up or down to a response. */ enable_feedback_loop: boolean; + + /** + * Optional. Only used when `enable_feedback_loop` == `true`. When set to `custom` the user will be presented with a text input + * to provide feedback. + */ + feedback_loop_type?: 'default' | 'custom'; } /** @@ -121,7 +133,7 @@ export class AI { * A text string that can be returned from an action to stop the AI system from continuing * to execute the current plan. * @remarks - * This command is incompatible and should not be used with `tools` augmentation + * This command is incompatible and should not be used with `tools` augmentation */ public static readonly StopCommandName = actions.StopCommandName; @@ -225,8 +237,11 @@ export class AI { this.defaultAction(AI.HttpErrorActionName, actions.httpError()); this.defaultAction(AI.PlanReadyActionName, actions.planReady()); this.defaultAction(AI.DoCommandActionName, actions.doCommand()); - this.defaultAction(AI.SayCommandActionName, actions.sayCommand(this._options.enable_feedback_loop)); this.defaultAction(AI.TooManyStepsActionName, actions.tooManySteps()); + this.defaultAction(AI.SayCommandActionName, actions.sayCommand( + this._options.enable_feedback_loop, + this._options.feedback_loop_type || 'default' + )); } /** diff --git a/js/packages/teams-ai/src/Application.spec.ts b/js/packages/teams-ai/src/Application.spec.ts index 513822f3a..f13d24ef8 100644 --- a/js/packages/teams-ai/src/Application.spec.ts +++ b/js/packages/teams-ai/src/Application.spec.ts @@ -11,7 +11,14 @@ import { MessageReactionTypes, TestAdapter, O365ConnectorCardActionQuery, - FileConsentCardResponse + FileConsentCardResponse, + TurnContext, + TeamsInfo, + ConversationReference, + TeamDetails, + ChannelInfo, + TeamsChannelAccount, + TeamsPagedMembersResult } from 'botbuilder'; import { @@ -920,4 +927,303 @@ describe('Application', () => { }); }); }); + + describe('getTeamChannels', () => { + let app = new Application(); + let stubContext: sinon.SinonStubbedInstance; + const returnedChannels: ChannelInfo[] = [{ id: 'testChannelId', name: 'testName' }]; + + beforeEach(() => { + app = new Application({ adapter: new TeamsAdapter() }); + stubContext = sandbox.createStubInstance(TurnContext); + const stubAdapter = sandbox.createStubInstance(CloudAdapter); + ( + stubAdapter.continueConversationAsync as unknown as sinon.SinonStub< + [string, Partial, (context: TurnContext) => Promise], + Promise + > + ).callsFake(async (fakeBotAppId, ref, logic) => { + await logic(stubContext); + }); + sandbox.stub(app, 'adapter').get(() => stubAdapter); + sandbox.stub(TeamsInfo, 'getTeamChannels').resolves(returnedChannels); + }); + + it('should return empty array if conversationType is not channel', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'personal', + id: 'testChannelId', + name: 'testName' + } + }); + const continueConversationAsyncStub = sandbox.stub(testAdapter, 'continueConversationAsync').resolves(); + + const channels = await app.getTeamChannels(new TurnContext(testAdapter, {})); + + assert.equal(channels.length, 0); + assert(continueConversationAsyncStub.notCalled); + }); + + it('should return channel array if conversationType is channel with defined teamId', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + channelData: { + team: { + id: 'testId' + } + } + }; + }); + + const channels = await app.getTeamChannels({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.deepEqual(channels, returnedChannels); + }); + + it('should return channel array if conversationType is channel with defined conversationId and undefined name', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + conversation: { + id: 'teamId' + } + }; + }); + + const channels = await app.getTeamChannels({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.deepEqual(channels, returnedChannels); + }); + + it('should return empty array if conversationType is channel with defined name', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + conversation: { + name: 'teamName', + id: 'teamId' + } + }; + }); + + const channels = await app.getTeamChannels({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.equal(channels.length, 0); + }); + }); + + describe('getTeamDetails', () => { + let app = new Application(); + let stubContext: sinon.SinonStubbedInstance; + const returnedDetails: TeamDetails = { + id: 'teamId', + name: 'teamName' + }; + + beforeEach(() => { + app = new Application({ adapter: new TeamsAdapter() }); + stubContext = sandbox.createStubInstance(TurnContext); + const stubAdapter = sandbox.createStubInstance(CloudAdapter); + ( + stubAdapter.continueConversationAsync as unknown as sinon.SinonStub< + [string, Partial, (context: TurnContext) => Promise], + Promise + > + ).callsFake(async (fakeBotAppId, ref, logic) => { + await logic(stubContext); + }); + sandbox.stub(app, 'adapter').get(() => stubAdapter); + sandbox.stub(TeamsInfo, 'getTeamDetails').resolves(returnedDetails); + }); + + it('should return undefined details if conversationType is not channel', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'personal', + id: 'testChannelId', + name: 'testName' + } + }); + const continueConversationAsyncStub = sandbox.stub(testAdapter, 'continueConversationAsync').resolves(); + + const details = await app.getTeamDetails(new TurnContext(testAdapter, {})); + + assert.equal(details, undefined); + assert(continueConversationAsyncStub.notCalled); + }); + + it('should return team details if conversationType is channel with defined teamId', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + channelData: { + team: { + id: 'testId' + } + } + }; + }); + + const details = await app.getTeamDetails({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.deepEqual(details, returnedDetails); + }); + + it('should return team details if conversationType is channel with defined conversationId and undefined name', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + conversation: { + id: 'teamId' + } + }; + }); + + const details = await app.getTeamDetails({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.deepEqual(details, returnedDetails); + }); + + it('should return undefined if conversationType is channel with defined name', async () => { + sandbox.stub(TurnContext, 'getConversationReference').returns({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + sandbox.stub(TurnContext.prototype, 'activity').get(() => { + return { + conversation: { + name: 'teamName', + id: 'teamId' + } + }; + }); + + const details = await app.getTeamDetails({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.equal(details, undefined); + }); + }); + + describe('getPagedMembers', () => { + let app = new Application(); + let stubContext: sinon.SinonStubbedInstance; + const returnedPagedMembers: TeamsPagedMembersResult = { + continuationToken: 'token', + members: [{} as TeamsChannelAccount, {} as TeamsChannelAccount] + }; + + beforeEach(() => { + app = new Application({ adapter: new TeamsAdapter() }); + stubContext = sandbox.createStubInstance(TurnContext); + const stubAdapter = sandbox.createStubInstance(CloudAdapter); + ( + stubAdapter.continueConversationAsync as unknown as sinon.SinonStub< + [string, Partial, (context: TurnContext) => Promise], + Promise + > + ).callsFake(async (fakeBotAppId, ref, logic) => { + await logic(stubContext); + }); + sandbox.stub(app, 'adapter').get(() => stubAdapter); + sandbox.stub(TeamsInfo, 'getPagedMembers').resolves(returnedPagedMembers); + }); + + it('should return paged members result', async () => { + const pagedMembers = await app.getPagedMembers({ + conversation: { + isGroup: false, + conversationType: 'channel', + id: 'testChannelId', + name: 'testName' + } + }); + + assert.deepEqual(pagedMembers, returnedPagedMembers); + }); + }); }); diff --git a/js/packages/teams-ai/src/Application.ts b/js/packages/teams-ai/src/Application.ts index 2dd9b59f0..a9ed1f646 100644 --- a/js/packages/teams-ai/src/Application.ts +++ b/js/packages/teams-ai/src/Application.ts @@ -10,11 +10,15 @@ import { Activity, ActivityTypes, BotAdapter, + ChannelInfo, ConversationReference, FileConsentCardResponse, O365ConnectorCardActionQuery, ResourceResponse, Storage, + TeamDetails, + TeamsInfo, + TeamsPagedMembersResult, TurnContext } from 'botbuilder'; @@ -23,6 +27,7 @@ import { ReadReceiptInfo } from 'botframework-connector'; import { AdaptiveCards, AdaptiveCardsOptions } from './AdaptiveCards'; import { AI, AIOptions } from './AI'; import { Meetings } from './Meetings'; +import { Messages } from './Messages'; import { MessageExtensions } from './MessageExtensions'; import { TaskModules, TaskModulesOptions } from './TaskModules'; import { AuthenticationManager, AuthenticationOptions } from './authentication/Authentication'; @@ -149,12 +154,14 @@ export interface FeedbackLoopData { /** * 'like' or 'dislike' */ - reaction: string; + reaction: 'like' | 'dislike'; + /** * The response the user provides when prompted with "What did you like/dislike?" after pressing one of the feedback buttons. */ - feedback: string; + feedback: string | Record; }; + /** * The activity ID that the feedback was provided on. */ @@ -241,6 +248,7 @@ export class Application { private readonly _invokeRoutes: AppRoute[] = []; private readonly _adaptiveCards: AdaptiveCards; private readonly _meetings: Meetings; + private readonly _messages: Messages; private readonly _messageExtensions: MessageExtensions; private readonly _taskModules: TaskModules; private readonly _ai?: AI; @@ -283,6 +291,7 @@ export class Application { } this._adaptiveCards = new AdaptiveCards(this); + this._messages = new Messages(this); this._messageExtensions = new MessageExtensions(this); this._meetings = new Meetings(this); this._taskModules = new TaskModules(this); @@ -350,6 +359,14 @@ export class Application { return this._authentication; } + /** + * Fluent interface for accessing Messages specific features. + * @returns {Messages} The Messages instance. + */ + public get messages(): Messages { + return this._messages; + } + /** * Fluent interface for accessing Message Extensions' specific features. * @returns {MessageExtensions} The MessageExtensions instance. @@ -514,14 +531,7 @@ export class Application { } // Identify conversation reference - let reference: Partial; - if (typeof (context as TurnContext).activity == 'object') { - reference = TurnContext.getConversationReference((context as TurnContext).activity); - } else if (typeof (context as Partial).type == 'string') { - reference = TurnContext.getConversationReference(context as Partial); - } else { - reference = context as Partial; - } + const reference: Partial = getConversationReference(context); await this.adapter.continueConversationAsync(this._options.botAppId ?? '', reference, logic); } @@ -899,6 +909,106 @@ export class Application { return response; } + /** + * Retrieves the list of team channels for a given context. + * @param context - The context of the conversation, which can be a TurnContext, + * Partial, or Partial. + * @returns A promise that resolves to an array of ChannelInfo objects if the bot is installed into a team, otherwise returns an empty array. + */ + public async getTeamChannels(context: TurnContext): Promise; + public async getTeamChannels(conversationReference: Partial): Promise; + public async getTeamChannels(activity: Partial): Promise; + public async getTeamChannels( + context: TurnContext | Partial | Partial + ): Promise { + let teamsChannels: ChannelInfo[] = []; + + // Identify conversation reference + const reference: Partial = getConversationReference(context); + + if (reference.conversation?.conversationType === 'channel') { + await this.continueConversationAsync(reference, async (ctx) => { + const teamId = + ctx.activity?.channelData?.team?.id ?? + (ctx.activity?.conversation?.name === undefined ? ctx.activity?.conversation?.id : undefined); + if (teamId) { + teamsChannels = await TeamsInfo.getTeamChannels(ctx, teamId); + } + }); + } + + return teamsChannels; + } + + /** + * Retrieves the team details for a given context. + * @param context - The context of the conversation, which can be a TurnContext, + * Partial, or Partial. + * @returns A promise that resolves to an array of ChannelInfo objects if the bot is installed into a team, otherwise returns an empty array. + */ + public async getTeamDetails(context: TurnContext): Promise; + public async getTeamDetails( + conversationReference: Partial + ): Promise; + public async getTeamDetails(activity: Partial): Promise; + public async getTeamDetails( + context: TurnContext | Partial | Partial + ): Promise { + let teamDetails: TeamDetails | undefined = undefined; + + // Identify conversation reference + const reference: Partial = getConversationReference(context); + + if (reference.conversation?.conversationType === 'channel') { + await this.continueConversationAsync(reference, async (ctx) => { + const teamId = + ctx.activity?.channelData?.team?.id ?? + (ctx.activity?.conversation?.name === undefined ? ctx.activity?.conversation?.id : undefined); + if (teamId) { + teamDetails = await TeamsInfo.getTeamDetails(ctx, teamId); + } + }); + } + + return teamDetails; + } + + /** + * Gets a paginated list of members of one-on-one, group, or team conversation. + * @param context - The context for the current turn with the user. + * @param {number} pageSize - Suggested number of entries on a page. Page sizes with less than 50 are treated as 50, and greater than 500, are capped at 500. + * @param {string} continuationToken - A continuation token. + * @returns The TeamsPagedMembersResult with the list of members. + */ + public async getPagedMembers( + context: TurnContext, + pageSize?: number, + continuationToken?: string + ): Promise; + public async getPagedMembers( + reference: Partial, + pageSize?: number, + continuationToken?: string + ): Promise; + public async getPagedMembers( + activity: Partial, + pageSize?: number, + continuationToken?: string + ): Promise; + public async getPagedMembers( + context: TurnContext | Partial | Partial, + pageSize?: number, + continuationToken?: string + ): Promise { + let pagedMembers: TeamsPagedMembersResult = { members: [], continuationToken: '' }; + await this.continueConversationAsync(context, async (ctx) => { + // Page sizes with less than 50 are treated as 50, and greater than 500, are capped at 500. + pagedMembers = await TeamsInfo.getPagedMembers(ctx, pageSize, continuationToken); + }); + + return pagedMembers; + } + /** * Manually start a timer to periodically send "typing" activities. * @remarks @@ -1367,6 +1477,47 @@ function createSignInSelector(startSignIn?: boolean | Selector): Selector { }; } +/** + * Retrieves a conversation reference from the given TurnContext. + * @param {TurnContext} context - The context to extract the conversation reference from. + * @returns {Partial} The extracted conversation reference. + */ +function getConversationReference(context: TurnContext): Partial; +/** + * Retrieves a conversation reference from the given activity. + * @param {Partial} activity - The activity to extract the conversation reference from. + * @returns {Partial} The extracted conversation reference. + */ +function getConversationReference(activity: Partial): Partial; +/** + * Retrieves a conversation reference from the given reference. + * @param {Partial} reference - The reference to extract the conversation reference from. + * @returns {Partial} The extracted conversation reference. + */ +function getConversationReference(reference: Partial): Partial; +/** + * Retrieves a conversation reference from the given context, activity, or reference. + * Overloaded function signatures: + * - getConversationReference(context: TurnContext): Partial + * - getConversationReference(activity: Partial): Partial + * - getConversationReference(reference: Partial): Partial + * @param {TurnContext | Partial | Partial} context - The context, activity, or reference to extract the conversation reference from. + * @returns {Partial} The extracted conversation reference. + */ +function getConversationReference( + context: TurnContext | Partial | Partial +): Partial { + let reference: Partial; + if (typeof (context as TurnContext).activity == 'object') { + reference = TurnContext.getConversationReference((context as TurnContext).activity); + } else if (typeof (context as Partial).type == 'string') { + reference = TurnContext.getConversationReference(context as Partial); + } else { + reference = context as Partial; + } + return reference; +} + /** * @private */ diff --git a/js/packages/teams-ai/src/Messages.spec.ts b/js/packages/teams-ai/src/Messages.spec.ts new file mode 100644 index 000000000..dc92ba5c5 --- /dev/null +++ b/js/packages/teams-ai/src/Messages.spec.ts @@ -0,0 +1,56 @@ +import sinon from 'sinon'; +import { strict as assert } from 'assert'; +import { ActivityTypes, Channels, INVOKE_RESPONSE_KEY, TestAdapter } from 'botbuilder'; + +import { Application } from './Application'; +import { createTestInvoke } from './internals/testing/TestUtilities'; +import { MessageInvokeNames, Messages } from './Messages'; + +describe('Messages', () => { + const adapter = new TestAdapter(); + let mockApp: Application; + + beforeEach(() => { + mockApp = new Application(); + sinon.stub(mockApp, 'adapter').get(() => adapter); + }); + + it('should exist when Application is instantiated', () => { + assert.notEqual(mockApp.messages, undefined); + assert.equal(mockApp.messages instanceof Messages, true); + }); + + describe(MessageInvokeNames.FETCH_INVOKE_NAME, () => { + it('fetch() with custom RouteSelector handler result is falsy', async () => { + const activity = createTestInvoke(MessageInvokeNames.FETCH_INVOKE_NAME, {}); + activity.channelId = Channels.Msteams; + mockApp.messages.fetch(async (_context, _state, _data) => { + return {}; + }); + + await adapter.processActivity(activity, async (context) => { + await mockApp.run(context); + const response = context.turnState.get(INVOKE_RESPONSE_KEY); + assert.deepEqual(response.value, { + status: 200, + body: { task: { type: 'continue', value: {} } } + }); + }); + }); + + it('fetch() with custom RouteSelector unhappy path', async () => { + const activity = { channelId: Channels.Msteams, type: ActivityTypes.Invoke, name: 'incorrectName' }; + const spy = sinon.spy(async (context, _state, _data) => { + return Promise.resolve(''); + }); + + mockApp.messages.fetch(spy); + + await adapter.processActivity(activity, async (context) => { + await mockApp.run(context); + }); + + assert.equal(spy.called, false); + }); + }); +}); diff --git a/js/packages/teams-ai/src/Messages.ts b/js/packages/teams-ai/src/Messages.ts new file mode 100644 index 000000000..1e00ccd25 --- /dev/null +++ b/js/packages/teams-ai/src/Messages.ts @@ -0,0 +1,100 @@ +/** + * @module teams-ai + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { + ActivityTypes, + Channels, + INVOKE_RESPONSE_KEY, + InvokeResponse, + TaskModuleResponse, + TaskModuleTaskInfo, + TurnContext +} from 'botbuilder'; +import { Application } from './Application'; +import { TurnState } from './TurnState'; + +export enum MessageInvokeNames { + FETCH_INVOKE_NAME = `message/fetchTask` +} + +/** + * TaskModules class to enable fluent style registration of handlers related to Task Modules. + * @template TState Type of the turn state object being persisted. + */ +export class Messages { + private readonly _app: Application; + + /** + * Creates a new instance of the TaskModules class. + * @param {Application} app Top level application class to register handlers with. + */ + public constructor(app: Application) { + this._app = app; + } + + /** + * Registers a handler to process the initial fetch of the task module. + * @remarks + * Handlers should respond with either an initial TaskInfo object or a string containing + * a message to display to the user. + * @template TData Optional. Type of the data object being passed to the handler. + * @param {(context: TurnContext, state: TState, data: TData) => Promise} handler - Function to call when the handler is triggered. + * @param {TurnContext} handler.context - Context for the current turn of conversation with the user. + * @param {TState} handler.state - Current state of the turn. + * @param {TData} handler.data - Data object passed to the handler. + * @returns {Application} The application for chaining purposes. + */ + public fetch = Record>( + handler: (context: TurnContext, state: TState, data: TData) => Promise + ): Application { + this._app.addRoute( + async (context) => { + return ( + context?.activity?.type === ActivityTypes.Invoke && + context?.activity?.name === MessageInvokeNames.FETCH_INVOKE_NAME + ); + }, + async (context, state) => { + if (context?.activity?.channelId === Channels.Msteams) { + const result = await handler(context, state, context.activity.value?.data ?? {}); + + if (!context.turnState.get(INVOKE_RESPONSE_KEY)) { + // Format invoke response + let response: TaskModuleResponse; + if (typeof result == 'string') { + // Return message + response = { + task: { + type: 'message', + value: result + } + }; + } else { + // Return card + response = { + task: { + type: 'continue', + value: result + } + }; + } + + // Queue up invoke response + await context.sendActivity({ + value: { body: response, status: 200 } as InvokeResponse, + type: ActivityTypes.InvokeResponse + }); + } + } + }, + true + ); + + return this._app; + } +} diff --git a/js/packages/teams-ai/src/StreamingResponse.ts b/js/packages/teams-ai/src/StreamingResponse.ts index 467685933..0d42a76f5 100644 --- a/js/packages/teams-ai/src/StreamingResponse.ts +++ b/js/packages/teams-ai/src/StreamingResponse.ts @@ -119,7 +119,7 @@ export class StreamingResponse { for (const citation of citations) { const clientCitation: ClientCitation = { '@type': 'Claim', - position: `${currPos + 1}`, + position: currPos + 1, appearance: { '@type': 'DigitalDocument', name: citation.title || `Document #${currPos + 1}`, diff --git a/js/packages/teams-ai/src/Utilities.spec.ts b/js/packages/teams-ai/src/Utilities.spec.ts index f019d6d6d..cce713c66 100644 --- a/js/packages/teams-ai/src/Utilities.spec.ts +++ b/js/packages/teams-ai/src/Utilities.spec.ts @@ -87,7 +87,7 @@ describe('Utilities', () => { const citations = [ { '@type': 'Claim', - position: '1', + position: 1, appearance: { '@type': 'DigitalDocument', name: 'the title', @@ -96,7 +96,7 @@ describe('Utilities', () => { }, { '@type': 'Claim', - position: '2', + position: 2, appearance: { '@type': 'DigitalDocument', name: 'the title', @@ -113,7 +113,7 @@ describe('Utilities', () => { const citations = [ { '@type': 'Claim', - position: '1', + position: 1, appearance: { '@type': 'DigitalDocument', name: 'the title', @@ -122,7 +122,7 @@ describe('Utilities', () => { }, { '@type': 'Claim', - position: '2', + position: 2, appearance: { '@type': 'DigitalDocument', name: 'the title', @@ -131,7 +131,7 @@ describe('Utilities', () => { }, { '@type': 'Claim', - position: '3', + position: 3, appearance: { '@type': 'DigitalDocument', name: 'the title', @@ -140,7 +140,7 @@ describe('Utilities', () => { }, { '@type': 'Claim', - position: '4', + position: 4, appearance: { '@type': 'DigitalDocument', name: 'the title', diff --git a/js/packages/teams-ai/src/actions/DoCommand.ts b/js/packages/teams-ai/src/actions/DoCommand.ts index 7b6fc0ca1..7c19f1bd2 100644 --- a/js/packages/teams-ai/src/actions/DoCommand.ts +++ b/js/packages/teams-ai/src/actions/DoCommand.ts @@ -34,6 +34,7 @@ export interface PredictedDoCommandAndHandler extends PredictedDoCommand /** * @private + * @returns {Promise} A promise that resolves to a string indicating whether the AI system should continue executing the plan. */ export function doCommand() { return async (context: TurnContext, state: TState, data: PredictedDoCommandAndHandler, action?: string) => { diff --git a/js/packages/teams-ai/src/actions/FlaggedInput.ts b/js/packages/teams-ai/src/actions/FlaggedInput.ts index d9e7a7d98..d13803e6b 100644 --- a/js/packages/teams-ai/src/actions/FlaggedInput.ts +++ b/js/packages/teams-ai/src/actions/FlaggedInput.ts @@ -10,6 +10,7 @@ import { StopCommandName } from './Action'; /** * @private + * @returns {Function} An async function that logs an error message and returns StopCommandName. */ export function flaggedInput() { return async () => { diff --git a/js/packages/teams-ai/src/actions/FlaggedOutput.ts b/js/packages/teams-ai/src/actions/FlaggedOutput.ts index c840d99eb..6a6bad0b3 100644 --- a/js/packages/teams-ai/src/actions/FlaggedOutput.ts +++ b/js/packages/teams-ai/src/actions/FlaggedOutput.ts @@ -10,8 +10,9 @@ import { StopCommandName } from './Action'; /** * @private + * @returns {() => Promise} A function that logs an error and returns the StopCommandName. */ -export function flaggedOutput() { +export function flaggedOutput(): () => Promise { return async () => { console.error( `The bots output has been moderated but no handler was registered for 'AI.FlaggedOutputActionName'.` diff --git a/js/packages/teams-ai/src/actions/HttpError.ts b/js/packages/teams-ai/src/actions/HttpError.ts index e2ff2b3b8..a4db23b8f 100644 --- a/js/packages/teams-ai/src/actions/HttpError.ts +++ b/js/packages/teams-ai/src/actions/HttpError.ts @@ -12,6 +12,7 @@ import { TurnState } from '../TurnState'; /** * @private + * @returns {Promise} A promise that resolves to a string. */ export function httpError() { return async (_context: TurnContext, _state: TState, err?: Error): Promise => { diff --git a/js/packages/teams-ai/src/actions/PlanReady.ts b/js/packages/teams-ai/src/actions/PlanReady.ts index 7f850b4fb..2c821cca5 100644 --- a/js/packages/teams-ai/src/actions/PlanReady.ts +++ b/js/packages/teams-ai/src/actions/PlanReady.ts @@ -14,6 +14,7 @@ import { StopCommandName } from './Action'; /** * @private + * @returns {Function} A function that takes TurnContext, TState, and Plan as arguments and returns a string or StopCommandName. */ export function planReady() { return async (_context: TurnContext, _state: TState, plan: Plan) => { diff --git a/js/packages/teams-ai/src/actions/SayCommand.spec.ts b/js/packages/teams-ai/src/actions/SayCommand.spec.ts index ea70ecbc9..7292b7179 100644 --- a/js/packages/teams-ai/src/actions/SayCommand.spec.ts +++ b/js/packages/teams-ai/src/actions/SayCommand.spec.ts @@ -188,7 +188,7 @@ describe('actions.sayCommand', () => { citation: [ { '@type': 'Claim', - position: '1', + position: 1, appearance: { '@type': 'DigitalDocument', name: 'the title', diff --git a/js/packages/teams-ai/src/actions/SayCommand.ts b/js/packages/teams-ai/src/actions/SayCommand.ts index d16e232e7..8af08452a 100644 --- a/js/packages/teams-ai/src/actions/SayCommand.ts +++ b/js/packages/teams-ai/src/actions/SayCommand.ts @@ -15,9 +15,13 @@ import { AIEntity, ClientCitation } from '../types'; /** * @private * @param {boolean} feedbackLoopEnabled - If true, the feedback loop UI for Teams will be enabled. + * @param {'default' | 'custom'} feedbackLoopType - the type of UI to use for feedback loop * @returns {''} - An empty string. */ -export function sayCommand(feedbackLoopEnabled: boolean = false) { +export function sayCommand( + feedbackLoopEnabled: boolean = false, + feedbackLoopType: 'default' | 'custom' = 'default', +) { return async (context: TurnContext, _state: TState, data: PredictedSayCommand) => { if (!data.response?.content) { return ''; @@ -37,7 +41,7 @@ export function sayCommand(feedbackLoopEna citations = data.response.context!.citations.map((citation, i) => { const clientCitation: ClientCitation = { '@type': 'Claim', - position: `${i + 1}`, + position: i + 1, appearance: { '@type': 'DigitalDocument', name: citation.title || `Document #${i + 1}`, @@ -54,6 +58,11 @@ export function sayCommand(feedbackLoopEna // If there are citations, filter out the citations unused in content. const referencedCitations = citations ? Utilities.getUsedCitations(contentText, citations) : undefined; + const channelData = feedbackLoopEnabled && feedbackLoopType ? { + feedbackLoop: { + type: feedbackLoopType + } + } : { feedbackLoopEnabled }; const entities: AIEntity[] = [ { @@ -65,10 +74,11 @@ export function sayCommand(feedbackLoopEna ...(referencedCitations ? { citation: referencedCitations } : {}) } ]; + const activity: Partial = { type: ActivityTypes.Message, text: contentText, - ...(isTeamsChannel ? { channelData: { feedbackLoopEnabled } } : {}), + ...(isTeamsChannel ? { channelData } : {}), entities: entities }; diff --git a/js/packages/teams-ai/src/actions/TooManySteps.ts b/js/packages/teams-ai/src/actions/TooManySteps.ts index e6bb64d01..8e3b050a5 100644 --- a/js/packages/teams-ai/src/actions/TooManySteps.ts +++ b/js/packages/teams-ai/src/actions/TooManySteps.ts @@ -13,6 +13,7 @@ import { TooManyStepsParameters } from '../types'; /** * @private + * @returns {Function} A function that checks if the AI system has exceeded the maximum number of steps or time allowed. */ export function tooManySteps() { return async (_context: TurnContext, _state: TState, data: TooManyStepsParameters) => { diff --git a/js/packages/teams-ai/src/actions/Unknown.ts b/js/packages/teams-ai/src/actions/Unknown.ts index 3f1fd780f..d195a7f3d 100644 --- a/js/packages/teams-ai/src/actions/Unknown.ts +++ b/js/packages/teams-ai/src/actions/Unknown.ts @@ -13,6 +13,7 @@ import { StopCommandName } from './Action'; /** * @private + * @returns {Function} An async function that logs an error and returns StopCommandName. */ export function unknown() { return async (_context: TurnContext, _state: TState, _data: any, action?: string) => { diff --git a/js/packages/teams-ai/src/augmentations/ToolsAugmentation.spec.ts b/js/packages/teams-ai/src/augmentations/ToolsAugmentation.spec.ts index 82dd580ee..588745946 100644 --- a/js/packages/teams-ai/src/augmentations/ToolsAugmentation.spec.ts +++ b/js/packages/teams-ai/src/augmentations/ToolsAugmentation.spec.ts @@ -158,5 +158,47 @@ describe('ToolsAugmentation', () => { assert(plan.commands.length === 0); }); }); + + it('should handle empty arguments gracefully', async () => { + await adapter.sendTextToBot('test', async (context) => { + const actionCalls: ActionCall[] = [ + { + type: 'function', + id: '123', + function: { + name: 'LightsOn', + arguments: '' + } + }, + { + type: 'function', + id: '124', + function: { + name: 'Pause', + arguments: '{ "time": 10 }' + } + } + ]; + const response: PromptResponse = { + status: 'success', + input: { + role: 'user', + content: 'Perform actions' + }, + message: { role: 'assistant', content: '', action_calls: actionCalls } + }; + const state = await TestTurnState.create(context); + const plan = await toolsAugmentation.createPlanFromResponse(context, state, response); + + assert(plan.type === 'plan'); + assert(plan.commands.length === 2); + assert(plan.commands[0].type === 'DO'); + assert((plan.commands[0] as PredictedDoCommand).action === 'LightsOn'); + assert.deepEqual((plan.commands[0] as PredictedDoCommand).parameters, {}); + assert(plan.commands[1].type === 'DO'); + assert((plan.commands[1] as PredictedDoCommand).action === 'Pause'); + assert.deepEqual((plan.commands[1] as PredictedDoCommand).parameters, { time: 10 }); + }); + }); }); }); diff --git a/js/packages/teams-ai/src/augmentations/ToolsAugmentation.ts b/js/packages/teams-ai/src/augmentations/ToolsAugmentation.ts index 38c1a4410..545ca1dfa 100644 --- a/js/packages/teams-ai/src/augmentations/ToolsAugmentation.ts +++ b/js/packages/teams-ai/src/augmentations/ToolsAugmentation.ts @@ -60,7 +60,7 @@ export class ToolsAugmentation implements Augmentation { * Creates a plan given validated response value. * @param {TurnContext} context Context for the current turn of conversation. * @param {Memory} memory An interface for accessing state variables. - * @param {PromptResponse} response The validated and transformed response for the prompt. + * @param {PromptResponse} response The validated and transformed response for the prompt. * @returns {Promise} The created plan. */ public createPlanFromResponse( @@ -74,14 +74,18 @@ export class ToolsAugmentation implements Augmentation { const actionToolCalls: ActionCall[] = response.message.action_calls; for (const toolCall of actionToolCalls) { - let parameters; + let parameters = {}; - try { - parameters = JSON.parse(toolCall.function.arguments) ?? {}; - } catch (err) { - console.error('ToolsAugmentation parameters:', toolCall.function.arguments); - console.error('ToolsAugmentation createPlanFromResponse: Error parsing tool arguments: ', err); - parameters = {}; + if (toolCall.function.arguments && toolCall.function.arguments.trim() !== '') { + try { + parameters = JSON.parse(toolCall.function.arguments); + } catch (err) { + console.warn( + `ToolsAugmentation: Error parsing tool arguments for ${toolCall.function.name}:`, + err + ); + console.warn('Arguments:', toolCall.function.arguments); + } } const doCommand: PredictedDoCommand = { diff --git a/js/packages/teams-ai/src/authentication/Authentication.ts b/js/packages/teams-ai/src/authentication/Authentication.ts index 573840d2f..9dc93076e 100644 --- a/js/packages/teams-ai/src/authentication/Authentication.ts +++ b/js/packages/teams-ai/src/authentication/Authentication.ts @@ -6,22 +6,24 @@ * Licensed under the MIT License. */ +import { type AuthenticationResult, ConfidentialClientApplication } from '@azure/msal-node'; import { Storage, TurnContext } from 'botbuilder'; import { OAuthPromptSettings } from 'botbuilder-dialogs'; -import { AuthenticationResult, ConfidentialClientApplication } from '@azure/msal-node'; -import { TurnState } from '../TurnState'; + import { Application, Selector } from '../Application'; -import { MessageExtensionAuthenticationBase } from './MessageExtensionAuthenticationBase'; -import { BotAuthenticationBase, deleteTokenFromState, setTokenInState } from './BotAuthenticationBase'; -import * as UserTokenAccess from './UserTokenAccess'; +import { TurnState } from '../TurnState'; + import { AdaptiveCardAuthenticationBase } from './AdaptiveCardAuthenticationBase'; -import { TeamsSsoSettings } from './TeamsSsoSettings'; -import { OAuthPromptMessageExtensionAuthentication } from './OAuthMessageExtensionAuthentication'; +import { BotAuthenticationBase, deleteTokenFromState, setTokenInState } from './BotAuthenticationBase'; +import { MessageExtensionAuthenticationBase } from './MessageExtensionAuthenticationBase'; +import { OAuthAdaptiveCardAuthentication } from './OAuthAdaptiveCardAuthentication'; import { OAuthBotAuthentication } from './OAuthBotAuthentication'; +import { OAuthPromptMessageExtensionAuthentication } from './OAuthMessageExtensionAuthentication'; +import { TeamsSsoAdaptiveCardAuthentication } from './TeamsSsoAdaptiveCardAuthentication'; import { TeamsSsoBotAuthentication } from './TeamsSsoBotAuthentication'; import { TeamsSsoMessageExtensionAuthentication } from './TeamsSsoMessageExtensionAuthentication'; -import { OAuthAdaptiveCardAuthentication } from './OAuthAdaptiveCardAuthentication'; -import { TeamsSsoAdaptiveCardAuthentication } from './TeamsSsoAdaptiveCardAuthentication'; +import { TeamsSsoSettings } from './TeamsSsoSettings'; +import * as UserTokenAccess from './UserTokenAccess'; /** * User authentication service. diff --git a/js/packages/teams-ai/src/planners/AssistantsPlanner.spec.ts b/js/packages/teams-ai/src/planners/AssistantsPlanner.spec.ts index 8ab1e459a..40a116844 100644 --- a/js/packages/teams-ai/src/planners/AssistantsPlanner.spec.ts +++ b/js/packages/teams-ai/src/planners/AssistantsPlanner.spec.ts @@ -1,571 +1,320 @@ -import { - Assistant, - AssistantThread, - AssistantThreadCreationOptions, - AssistantsClient, - CreateMessageOptions, - CreateRunOptions, - CreateRunRequestOptions, - CreateThreadOptions, - GetRunOptions, - ListMessagesOptions, - ListResponseOf, - ListRunsOptions, - MessageRole, - OpenAIKeyCredential, - RequiredAction, - RequiredFunctionToolCall, - SubmitToolOutputsToRunOptions, - ThreadMessage, - ThreadRun, - ToolOutput -} from '@azure/openai-assistants'; -import { TestAdapter, TurnContext } from 'botbuilder-core'; -import { Activity } from 'botframework-schema'; +/// + +import assert from 'assert'; +import OpenAI from 'openai'; +import { TurnContext, TestAdapter } from 'botbuilder-core'; +import sinon from 'sinon'; + import { TurnState } from '../TurnState'; import { AssistantsPlanner, AssistantsPlannerOptions } from './AssistantsPlanner'; -import { AI, AIOptions } from '../AI'; -import assert from 'assert'; +import { AI } from '../AI'; import { PredictedDoCommand, PredictedSayCommand } from './Planner'; describe('AssistantsPlanner', () => { - const createTurnContextAndState = async (activity: Partial): Promise<[TurnContext, TurnState]> => { + let planner: AssistantsPlanner; + let openAIStub: sinon.SinonStubbedInstance; + let context: TurnContext; + let state: TurnState; + let ai: AI; + + beforeEach(async () => { + openAIStub = sinon.createStubInstance(OpenAI); + openAIStub.beta = { + threads: { + create: sinon.stub().resolves({ id: 'test-thread-id' }), + runs: { + create: sinon.stub().resolves({ id: 'test-run-id', status: 'completed' }), + createAndPoll: sinon.stub().resolves({ id: 'test-run-id', status: 'completed' }), + retrieve: sinon.stub().resolves({ id: 'test-run-id', status: 'completed' }), + submitToolOutputs: sinon.stub().resolves({ id: 'test-run-id', status: 'completed' }), + submitToolOutputsAndPoll: sinon.stub().resolves({ id: 'test-run-id', status: 'completed' }), + list: sinon.stub().resolves({ + data: [ + { + id: 'test-run-id', + status: 'completed' + } + ] + }) + }, + messages: { + create: sinon.stub().resolves({ id: 'test-message-id' }), + list: sinon.stub().resolves({ + data: [ + { + id: 'test-message-id', + content: [{ type: 'text', text: { value: 'Test response' } }] + } + ] + }) + } + } + } as unknown as OpenAI['beta']; + + const options: AssistantsPlannerOptions = { + apiKey: 'test-key', + assistant_id: 'test-assistant-id' + }; + + planner = new AssistantsPlanner(options); + (planner as any)._client = openAIStub; + const testAdapter = new TestAdapter(); - const context = new TurnContext(testAdapter, { - channelId: 'msteams', - recipient: { - id: 'bot', - name: 'bot' - }, - from: { - id: 'user', - name: 'user' - }, - conversation: { - id: 'convo', - isGroup: false, - conversationType: 'personal', - name: 'convo' - }, - ...activity + context = new TurnContext(testAdapter, { + type: 'message', + text: 'test input', + channelId: 'test', + from: { id: 'user', name: 'User' }, + recipient: { id: 'bot', name: 'Bot' }, + conversation: { id: 'conversation', isGroup: false, conversationType: 'personal', name: 'Conversation' }, + channelData: {} }); - const state: TurnState = new TurnState(); + + state = new TurnState(); await state.load(context); state.temp = { - input: '', + input: 'test input', inputFiles: [], lastOutput: '', actionOutputs: {}, authTokens: {} }; - return [context, state]; - }; - - describe('beginTask', async () => { - it('expects a single reply', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('welcome'); - - const plan = await planner.beginTask(context, state, ai); - - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 1); - assert.equal(plan.commands[0].type, 'SAY'); - assert.equal('welcome', (plan.commands[0] as PredictedSayCommand).response.content); - }); - - it('waits for current run', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; + ai = new AI({ planner }); + }); - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); + afterEach(() => { + sinon.restore(); + }); - testClient.remainingRunStatus.push('in_progress'); - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('welcome'); + describe('beginTask', () => { + it('should create a thread and run, then return a plan', async () => { + (openAIStub.beta.threads.messages.list as sinon.SinonStub).resolves({ + data: [ + { + id: 'test-message-id-2', + content: [{ type: 'text', text: { value: 'New response' } }] + }, + { + id: 'test-message-id', + content: [{ type: 'text', text: { value: 'Test response' } }] + } + ] + }); const plan = await planner.beginTask(context, state, ai); - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 1); - assert.equal(plan.commands[0].type, 'SAY'); - assert.equal('welcome', (plan.commands[0] as PredictedSayCommand).response.content); + sinon.assert.calledOnce(openAIStub.beta.threads.create as sinon.SinonStub); + sinon.assert.calledWith(openAIStub.beta.threads.messages.create as sinon.SinonStub, 'test-thread-id', { + role: 'user', + content: 'test input' + }); + sinon.assert.calledWith(openAIStub.beta.threads.runs.createAndPoll as sinon.SinonStub, 'test-thread-id', { + assistant_id: 'test-assistant-id' + }); + + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 1); + assert.strictEqual(plan.commands[0].type, 'SAY'); + assert.strictEqual((plan.commands[0] as PredictedSayCommand).response.content, 'New response'); }); - it('waits for previous run', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - testClient.remainingRunStatus.push('failed'); - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('welcome'); - - const thread = await testClient.createThread({}); - await testClient.createRun(thread.id, { assistantId: 'assistant_id' }); - state.setValue('conversation.assistants_state', { threadId: thread.id }); + it('should handle run cancellation', async () => { + (openAIStub.beta.threads.runs.retrieve as sinon.SinonStub).resolves({ + id: 'test-run-id', + status: 'cancelled' + }); const plan = await planner.beginTask(context, state, ai); - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 1); - assert.equal(plan.commands[0].type, 'SAY'); - assert.equal('welcome', (plan.commands[0] as PredictedSayCommand).response.content); + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 0); }); - it('run cancelled', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - testClient.remainingRunStatus.push('cancelled'); - testClient.remainingMessages.push('welcome'); + it('should handle run expiration', async () => { + (openAIStub.beta.threads.runs.retrieve as sinon.SinonStub).resolves({ + id: 'test-run-id', + status: 'expired' + }); const plan = await planner.beginTask(context, state, ai); - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 0); + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 1); + assert.strictEqual(plan.commands[0].type, 'DO'); + assert.strictEqual((plan.commands[0] as PredictedDoCommand).action, AI.TooManyStepsActionName); }); - it('run expired', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - testClient.remainingRunStatus.push('expired'); - testClient.remainingMessages.push('welcome'); - - const plan = await planner.beginTask(context, state, ai); - - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 1); - assert.equal(plan.commands[0].type, 'DO'); - assert.equal((plan.commands[0] as PredictedDoCommand).action, AI.TooManyStepsActionName); - }); + it('should throw an error on run failure', async () => { + (openAIStub.beta.threads.runs.retrieve as sinon.SinonStub).resolves({ + id: 'test-run-id', + status: 'failed', + last_error: { code: 'test_error', message: 'Test error message' } + }); - it('run failed', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient + await assert.rejects( + () => planner.beginTask(context, state, ai), + /Run failed failed. ErrorCode: test_error. ErrorMessage: Test error message/ ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - testClient.remainingRunStatus.push('failed'); - testClient.remainingMessages.push('welcome'); - - try { - await planner.beginTask(context, state, ai); - } catch (e) { - assert((e as Error).message.indexOf('Run failed') >= 0); - return; - } - - assert.fail(); }); }); - describe('continueTask()', () => { - it('requires action', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - const functionToolCall: RequiredFunctionToolCall = { - type: 'function', - id: 'test-tool-id', - function: { - name: 'test-action', - arguments: '{}', - output: null - } - }; - const requiredAction: RequiredAction = { - type: 'submit_tool_outputs', - submitToolOutputs: { - toolCalls: [functionToolCall] - } - }; - - testClient.remainingActions.push(requiredAction); - testClient.remainingRunStatus.push('requires_action'); - testClient.remainingRunStatus.push('in_progress'); - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('welcome'); - - const plan1 = await planner.continueTask(context, state, ai); - state.temp.actionOutputs['test-action'] = 'test-output'; - const plan2 = await planner.continueTask(context, state, ai); - - assert(plan1); - assert(plan1.commands); - assert.equal(plan1.commands.length, 1); - assert.equal(plan1.commands[0].type, 'DO'); - assert.equal((plan1.commands[0] as PredictedDoCommand).action, 'test-action'); - assert(plan2); - assert(plan2.commands); - assert.equal(plan2.commands[0].type, 'SAY'); - assert.equal((plan2.commands[0] as PredictedSayCommand).response.content, 'welcome'); - - const toolMap: { [key: string]: string } = state.getValue('temp.submitToolMap'); - assert(toolMap); - assert.equal(Object.keys(toolMap).length, 1); - assert('test-action' in toolMap); - assert(toolMap['test-action'], 'test-tool_id'); - }); + describe('continueTask', () => { + it('should continue an existing thread and run', async () => { + // Setup the initial state + state.setValue('conversation.assistants_state', { + thread_id: 'existing-thread-id', + run_id: 'existing-run-id', + last_message_id: 'existing-message-id' + }); + + // Setup action outputs + state.temp.actionOutputs = { test_function: 'test output' }; + state.setValue('temp.submitToolMap', { test_function: 'test-tool-call-id' }); + state.setValue('temp.submitToolOutputs', true); + + // Mock messages list response + (openAIStub.beta.threads.messages.list as sinon.SinonStub).resolves({ + data: [ + { + id: 'new-message-id', + content: [{ type: 'text', text: { value: 'New response' } }] + }, + { + id: 'existing-message-id', + content: [{ type: 'text', text: { value: 'Old response' } }] + } + ] + }); - it('ignores redundant action', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient - ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - state.temp.actionOutputs['other-action'] = 'should not be used'; - - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); - - const functionToolCall: RequiredFunctionToolCall = { - type: 'function', - id: 'test-tool-id', - function: { - name: 'test-action', - arguments: '{}', - output: null - } - }; - const requiredAction: RequiredAction = { - type: 'submit_tool_outputs', - submitToolOutputs: { - toolCalls: [functionToolCall] - } - }; - - testClient.remainingActions.push(requiredAction); - testClient.remainingRunStatus.push('requires_action'); - testClient.remainingRunStatus.push('in_progress'); - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('welcome'); - - const plan1 = await planner.continueTask(context, state, ai); - state.temp.actionOutputs['test-action'] = 'test-output'; - const plan2 = await planner.continueTask(context, state, ai); - - assert(plan1); - assert(plan1.commands); - assert.equal(plan1.commands.length, 1); - assert.equal(plan1.commands[0].type, 'DO'); - assert.equal((plan1.commands[0] as PredictedDoCommand).action, 'test-action'); - assert(plan2); - assert(plan2.commands); - assert.equal(plan2.commands[0].type, 'SAY'); - assert.equal((plan2.commands[0] as PredictedSayCommand).response.content, 'welcome'); - - const toolMap: { [key: string]: string } = state.getValue('temp.submitToolMap'); - assert(toolMap); - assert.equal(Object.keys(toolMap).length, 1); - assert('test-action' in toolMap); - assert(toolMap['test-action'], 'test-tool_id'); - }); + const plan = await planner.continueTask(context, state, ai); - it('handles multiple messages', async () => { - const testClient = new TestAssistantsClient(); - const planner = new TestAssistantsPlanner( - { apiKey: 'test-key', assistant_id: 'test-assistant-id' }, - testClient + // Verify submitToolOutputsAndPoll was called with correct arguments + sinon.assert.calledWith( + openAIStub.beta.threads.runs.submitToolOutputsAndPoll as sinon.SinonStub, + 'existing-thread-id', + 'existing-run-id', + { + tool_outputs: [ + { + tool_call_id: 'test-tool-call-id', + output: 'test output' + } + ] + } ); - const [context, state] = await createTurnContextAndState({}); - state.temp.input = 'hello'; - state.temp.actionOutputs['other-action'] = 'should not be used'; - const aiOptions: AIOptions = { - planner: planner - }; - const ai = new AI(aiOptions); + // Verify the plan structure + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 1); + assert.strictEqual(plan.commands[0].type, 'SAY'); + assert.strictEqual((plan.commands[0] as PredictedSayCommand).response.content, 'New response'); + }); - testClient.remainingRunStatus.push('completed'); - testClient.remainingMessages.push('message 2'); - testClient.remainingMessages.push('message 1'); - testClient.remainingMessages.push('welcome'); + it('should handle required actions', async () => { + (openAIStub.beta.threads.runs.retrieve as sinon.SinonStub).resolves({ + id: 'test-run-id', + status: 'requires_action', + required_action: { + type: 'submit_tool_outputs', + submit_tool_outputs: { + tool_calls: [ + { + id: 'test-tool-call-id', + type: 'function', + function: { + name: 'test_function', + arguments: '{"arg1": "value1"}' + } + } + ] + } + } + }); const plan = await planner.continueTask(context, state, ai); - assert(plan); - assert(plan.commands); - assert.equal(plan.commands.length, 3); - assert.equal(plan.commands[0].type, 'SAY'); - assert.equal((plan.commands[0] as PredictedSayCommand).response.content, 'message 2'); - assert.equal((plan.commands[1] as PredictedSayCommand).response.content, 'message 1'); - assert.equal((plan.commands[2] as PredictedSayCommand).response.content, 'welcome'); + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 1); + assert.strictEqual(plan.commands[0].type, 'DO'); + assert.strictEqual((plan.commands[0] as PredictedDoCommand).action, 'test_function'); + assert.deepStrictEqual((plan.commands[0] as PredictedDoCommand).parameters, { arg1: 'value1' }); }); }); -}); -class TestAssistantsPlanner extends AssistantsPlanner { - public constructor(options: AssistantsPlannerOptions, client: AssistantsClient) { - options.polling_interval = 2; - super(options); - this._client = client; - } -} - -class TestAssistantsClient extends AssistantsClient { - private _threads: AssistantThread[]; - private _messages: { [key: string]: ThreadMessage[] }; - private _runs: { [key: string]: ThreadRun[] }; - public remainingActions: RequiredAction[]; - public remainingRunStatus: string[]; - public remainingMessages: string[]; - - private _assistant: Assistant; - - public constructor() { - super(new OpenAIKeyCredential('api-key')); - this._threads = []; - this._messages = {}; - this._runs = {}; - this.remainingActions = []; - this.remainingRunStatus = []; - this.remainingMessages = []; - this._assistant = { - id: 'assistant_id', - createdAt: new Date(), - name: 'test assistant', - description: 'test assistant description', - model: 'test model', - instructions: 'test instructions', - tools: [], - fileIds: [], - metadata: null - }; - } - - public override async createMessage( - threadId: string, - role: MessageRole, - content: string, - options?: CreateMessageOptions - ): Promise { - const newMessage: ThreadMessage = { - id: Date.now().toString(), - createdAt: new Date(), - threadId: threadId, - role: role, - content: [{ type: 'text', text: { value: content, annotations: [] } }], - assistantId: this._assistant?.id, - runId: '', - fileIds: options?.fileIds, - metadata: null - }; - - if (threadId in this._messages) { - this._messages[threadId].push(newMessage); - } else { - this._messages[threadId] = [newMessage]; - } - - return newMessage; - } - - public override async createThread( - body?: AssistantThreadCreationOptions, - options?: CreateThreadOptions - ): Promise { - const newThread: AssistantThread = { - id: Date.now().toString(), - createdAt: new Date(), - metadata: body!.metadata ?? null - }; + describe('submitActionResults', () => { + it('should submit action results and return a plan', async () => { + // Setup the initial state + state.setValue('conversation.assistants_state', { + thread_id: 'test-thread-id', + run_id: 'test-run-id', + last_message_id: 'test-message-id' + }); + + // Setup action outputs + state.temp.actionOutputs = { test_function: 'test output' }; + state.setValue('temp.submitToolMap', { test_function: 'test-tool-call-id' }); + + // Mock messages list response with messages before last_message_id + (openAIStub.beta.threads.messages.list as sinon.SinonStub).resolves({ + data: [ + { + id: 'new-message-id', + content: [{ type: 'text', text: { value: 'New response' } }] + }, + { + id: 'test-message-id', + content: [{ type: 'text', text: { value: 'Old response' } }] + } + ] + }); + + // Mock the run completion + (openAIStub.beta.threads.runs.submitToolOutputsAndPoll as sinon.SinonStub).resolves({ + id: 'test-run-id', + status: 'completed' + }); + + const plan = await (planner as any).submitActionResults(context, state, ai); + + // Verify submitToolOutputsAndPoll was called with correct arguments + sinon.assert.calledWith( + openAIStub.beta.threads.runs.submitToolOutputsAndPoll as sinon.SinonStub, + 'test-thread-id', + 'test-run-id', + { + tool_outputs: [ + { + tool_call_id: 'test-tool-call-id', + output: 'test output' + } + ] + } + ); - const newMessages: ThreadMessage[] = []; - const len = body?.messages?.length ?? 0; - for (let i = 0; i < len; i++) { - const m = body!.messages![i]; - const newMessage: ThreadMessage = { - role: m.role, - id: Date.now.toString(), - createdAt: new Date(), - threadId: newThread.id, - content: [{ type: 'text', text: { value: m.content, annotations: [] } }], - assistantId: this._assistant?.id, - runId: '', - fileIds: [], - metadata: null - }; - newMessages.push(newMessage); - } - - this._messages[newThread.id] = newMessages; - this._threads.push(newThread); - return Promise.resolve(newThread); - } - - public override async listMessages( - thread_id: string, - options?: ListMessagesOptions - ): Promise> { - while (this.remainingMessages.length > 0) { - const nextMessage = this.remainingMessages.shift(); // Removes the first element from the list - this.createMessage(thread_id, 'user', nextMessage!); - } - - const lastMessageId = options?.before; - const i = this._messages[thread_id].findIndex((m) => m.id == lastMessageId); - const filteredMessages = this._messages[thread_id].slice(i + 1); - - // the elements are in ascending order of the creation timestamp - filteredMessages.reverse(); - - return Promise.resolve({ - data: filteredMessages, - firstId: '', - lastId: '', - hasMore: false + // Verify the plan structure + assert.strictEqual(plan.type, 'plan'); + assert.strictEqual(plan.commands.length, 1); + assert.strictEqual(plan.commands[0].type, 'SAY'); + assert.strictEqual((plan.commands[0] as PredictedSayCommand).response.content, 'New response'); }); - } - - public async createRun( - thread_id: string, - createRunOptions: CreateRunOptions, - options?: CreateRunRequestOptions - ): Promise { - let remainingActions: RequiredAction; - - if (this.remainingActions.length > 0) { - remainingActions = this.remainingActions.shift()!; - } - - const newRun: ThreadRun = { - id: Date.now().toString(), - threadId: thread_id, - assistantId: this._assistant!.id, - status: 'in_progress', - requiredAction: remainingActions!, - model: this._assistant?.model ?? 'test-model', - instructions: this._assistant?.instructions ?? 'instructions', - tools: this._assistant?.tools ?? [], - fileIds: [], - createdAt: new Date(), - expiresAt: new Date(), - startedAt: new Date(), - completedAt: new Date(), - cancelledAt: new Date(), - failedAt: new Date(), - metadata: null - }; - - if (thread_id in this._runs) { - this._runs[thread_id].push(newRun); - } else { - this._runs[thread_id] = [newRun]; - } - - return Promise.resolve(newRun); - } - - public override async getRun(threadId: string, runId: string, options?: GetRunOptions): Promise { - if (this._runs[threadId].length == 0) { - return Promise.reject(); - } - - const runStatus = this.remainingRunStatus.shift(); // dequeue - const i = this._runs[threadId].findIndex((r) => r.id == runId); - - const run = this._runs[threadId][i]; - run.status = runStatus!; - - return Promise.resolve(run); - } + }); - public override async listRuns(threadId: string, options?: ListRunsOptions): Promise> { - const runs = this._runs[threadId] ?? []; - return { - data: runs, - firstId: '', - lastId: '', - hasMore: false - }; - } - - public override async submitToolOutputsToRun( - threadId: string, - runId: string, - toolOutputs: ToolOutput[], - options?: SubmitToolOutputsToRunOptions - ): Promise { - return Promise.resolve(this.getRun(threadId, runId)); - } -} + describe('blockOnInProgressRuns', () => { + it('should wait for in-progress runs to complete', async () => { + (openAIStub.beta.threads.runs.list as sinon.SinonStub) + .onFirstCall() + .resolves({ + data: [{ id: 'run-1', status: 'in_progress' }] + }) + .onSecondCall() + .resolves({ + data: [{ id: 'run-1', status: 'completed' }] + }); + + await (planner as any).blockOnInProgressRuns('test-thread-id'); + + sinon.assert.calledWith(openAIStub.beta.threads.runs.list as sinon.SinonStub, 'test-thread-id'); + }); + }); +}); diff --git a/js/packages/teams-ai/src/planners/AssistantsPlanner.ts b/js/packages/teams-ai/src/planners/AssistantsPlanner.ts index 376da2c16..b6e4e5dd5 100644 --- a/js/packages/teams-ai/src/planners/AssistantsPlanner.ts +++ b/js/packages/teams-ai/src/planners/AssistantsPlanner.ts @@ -6,21 +6,13 @@ * Licensed under the MIT License. */ -import { Planner, Plan, PredictedDoCommand, PredictedSayCommand } from './Planner'; -import { TurnState } from '../TurnState'; import { TurnContext } from 'botbuilder'; +import OpenAI, { AzureClientOptions, AzureOpenAI } from 'openai'; + import { AI } from '../AI'; -import { - Assistant, - AssistantCreationOptions, - AssistantsClient, - AzureKeyCredential, - OpenAIKeyCredential, - RequiredAction, - ThreadMessage, - ThreadRun, - ToolOutput -} from '@azure/openai-assistants'; +import { TurnState } from '../TurnState'; + +import { Planner, Plan, PredictedDoCommand, PredictedSayCommand } from './Planner'; /** * @private @@ -47,17 +39,19 @@ const SUBMIT_TOOL_OUTPUTS_MAP = 'temp.submitToolMap'; */ export interface AssistantsPlannerOptions { /** - * The OpenAI or Azure OpenAI API key. + * The OpenAI or Azure OpenAI API key. Required. */ apiKey: string; /** * The Azure OpenAI resource endpoint. + * @remarks + * Required when using Azure OpenAI. Not used for OpenAI. */ endpoint?: string; /** - * The ID of the assistant to use. + * The ID of the assistant to use. Required. */ assistant_id: string; @@ -77,34 +71,38 @@ export interface AssistantsPlannerOptions { } /** - * A Planner that uses the OpenAI Assistants API. + * A Planner that uses the OpenAI Assistants API to generate plans for the AI system. * @template TState Optional. Type of application state. + * @remarks + * This planner manages conversations through OpenAI's thread-based system, handling: + * - Thread creation and management + * - Message submission + * - Tool/function calling + * - Response processing */ export class AssistantsPlanner implements Planner { private readonly _options: AssistantsPlannerOptions; - protected _client: AssistantsClient; - protected _assistant?: Assistant; + private _client: OpenAI; /** * Creates a new `AssistantsPlanner` instance. * @param {AssistantsPlannerOptions} options - Options for configuring the AssistantsPlanner. */ public constructor(options: AssistantsPlannerOptions) { - this._options = Object.assign( - { - polling_interval: DEFAULT_POLLING_INTERVAL, - assistants_state_variable: DEFAULT_ASSISTANTS_STATE_VARIABLE - }, - options - ); + this._options = { + polling_interval: DEFAULT_POLLING_INTERVAL, + assistants_state_variable: DEFAULT_ASSISTANTS_STATE_VARIABLE, + ...options + }; - this._client = AssistantsPlanner.createClient(options.apiKey, options.endpoint); + this._client = AssistantsPlanner.createClient(options.apiKey, options.endpoint, this._options); } /** * Starts a new task. * @remarks - * This method is called when the AI system is ready to start a new task. The planner should + * This method is called when the AI system is ready to start a new task. It delegates the + * task handling to the `continueTask` method. The planner should * generate a plan that the AI system will execute. Returning an empty plan signals that * there is no work to be performed. * @@ -114,17 +112,22 @@ export class AssistantsPlanner implements * @param {AI} ai - The AI system that is generating the plan. * @returns {Promise} The plan that was generated. */ - public beginTask(context: TurnContext, state: TState, ai: AI): Promise { - return this.continueTask(context, state, ai); + public async beginTask(context: TurnContext, state: TState, ai: AI): Promise { + const threadId = await this.ensureThreadCreated(state, context.activity.text); + await this.blockOnInProgressRuns(threadId); + return await this.submitUserInput(context, state, ai); } /** * Continues the current task. * @remarks - * This method is called when the AI system has finished executing the previous plan and is - * ready to continue the current task. The planner should generate a plan that the AI system - * will execute. Returning an empty plan signals that the task is completed and there is no work - * to be performed. + * This method is called when the AI system is ready to continue the current task. It handles: + * - Creating a new thread if one doesn't exist + * - Submitting tool outputs if required + * - Waiting for any in-progress runs to complete + * - Submitting user input and creating a new run + * The method generates a plan that the AI system will execute. Returning an empty plan signals + * that the task is completed and there is no work to be performed. * * The output from the last plan step that was executed is passed to the planner via `state.temp.input`. * @param {TurnContext} context - Context for the current turn of conversation. @@ -133,260 +136,250 @@ export class AssistantsPlanner implements * @returns {Promise} The plan that was generated. */ public async continueTask(context: TurnContext, state: TState, ai: AI): Promise { - // Create a new thread if we don't have one already - const thread_id = await this.ensureThreadCreated(state, context.activity.text); - - // Add the users input to the thread or send tool outputs - if (state.getValue(SUBMIT_TOOL_OUTPUTS_VARIABLE) == true) { - // Send the tool output to the assistant - return await this.submitActionResults(context, state, ai); - } else { - // Wait for any current runs to complete since you can't add messages or start new runs - // if there's already one in progress - await this.blockOnInProgressRuns(thread_id); - - // Submit user input - return await this.submitUserInput(context, state, ai); - } + return await this.submitActionResults(context, state, ai); } /** - * Static helper method for programmatically creating an assistant. + * Creates a new assistant using the OpenAI Assistants API. * @param {string} apiKey - OpenAI API key. - * @param {AssistantCreationOptions} request - Definition of the assistant to create. - * @param {string} endpoint - The Azure OpenAI resource endpoint. - * @returns {Promise} The created assistant. + * @param {OpenAI.Beta.AssistantCreateParams} request - Definition of the assistant to create. + * @param {string} endpoint - Optional. The Azure OpenAI resource endpoint. Required when using Azure OpenAI. + * @param {Record} azureClientOptions - Optional. The Azure OpenAI client options. + * @returns {Promise} The created assistant. + * @throws {Error} If the assistant creation fails. */ public static async createAssistant( apiKey: string, - request: AssistantCreationOptions, - endpoint?: string - ): Promise { - const client = AssistantsPlanner.createClient(apiKey, endpoint); - return await client.createAssistant(request); + request: OpenAI.Beta.AssistantCreateParams, + endpoint?: string, + azureClientOptions?: AzureClientOptions + ): Promise { + const client = AssistantsPlanner.createClient(apiKey, endpoint, azureClientOptions); + return await client.beta.assistants.create(request); } /** + * Ensures a thread exists for the current conversation. * @private - * Exposed for unit testing. - * @param {string} thread_id The thread id to retrieve the run. - * @returns {Promise} The retrieved run. + * @param {TurnState} state - The application Turn State. + * @param {string} input - The initial thread input. + * @returns {Promise} The thread ID. */ - protected async retrieveLastRun(thread_id: string): Promise { - const list = await this._client.listRuns(thread_id, { limit: 1 }); - if (list.data.length > 0) { - return list.data[0]; + private async ensureThreadCreated(state: TState, input: string): Promise { + const assistantsState = this.ensureAssistantsState(state); + if (assistantsState.thread_id == null) { + const thread = await this._client.beta.threads.create(); + assistantsState.thread_id = thread.id; + this.updateAssistantsState(state, assistantsState); } - return null; + return assistantsState.thread_id; } /** + * Checks if a run has reached a terminal state. * @private - * @param {ThreadRun} run - The run to be checked for completion. - * @returns {boolean} True if completed, otherwise false. + * @param {OpenAI.Beta.Threads.Run} run - The run to check. + * @returns {boolean} True if the run is in a completed state. */ - private isRunCompleted(run: ThreadRun): boolean { - switch (run.status) { - case 'completed': - case 'failed': - case 'cancelled': - case 'expired': - return true; - } - - return false; + private isRunCompleted(run: OpenAI.Beta.Threads.Run): boolean { + return ['completed', 'failed', 'cancelled', 'expired'].includes(run.status); } /** * @private - * @param {string} thread_id - The current thread id. - * @param {string} run_id - The run id. - * @param {boolean} handleActions - Whether to handle actions. False by default. - * @returns {Promise} The current Run. + * Waits for a run to complete. + * @param {string} thread_id - The ID of the thread. + * @param {string} run_id - The ID of the run. + * @param {boolean} handleActions - Whether to handle actions. + * @returns {Promise} The completed run. */ - private async waitForRun(thread_id: string, run_id: string, handleActions = false): Promise { - // Wait for the run to complete + private async waitForRun( + thread_id: string, + run_id: string, + handleActions = false + ): Promise { while (true) { await new Promise((resolve) => setTimeout(resolve, this._options.polling_interval)); - const run = await this._client.getRun(thread_id, run_id); - switch (run.status) { - case 'requires_action': - if (handleActions) { - return run; - } - break; - case 'cancelled': - case 'failed': - case 'completed': - case 'expired': - return run; + const run = await this._client.beta.threads.runs.retrieve(thread_id, run_id); + if ((run.status === 'requires_action' && handleActions) || this.isRunCompleted(run)) { + return run; } } } /** * @private - * @param {TurnContext} context - The application Turn Context. - * @param {TState} state - The application Turn State. + * Submits action results to the assistant. + * @param {TurnContext} context - The turn context. + * @param {TState} state - The turn state. * @param {AI} ai - The AI instance. - * @returns {Promise} The action results as Plan. + * @returns {Promise} A plan based on the action results. */ private async submitActionResults(context: TurnContext, state: TState, ai: AI): Promise { - // Get the current assistant state - const assistants_state = this.ensureAssistantsState(state); + const assistantsState = this.ensureAssistantsState(state); - // Map the action outputs to tool outputs const actionOutputs = state.temp.actionOutputs; const toolMap = state.getValue(SUBMIT_TOOL_OUTPUTS_MAP) as { [key: string]: string }; - const toolOutputs: ToolOutput[] = []; + const toolOutputs: OpenAI.Beta.Threads.Runs.RunSubmitToolOutputsParams.ToolOutput[] = []; for (const action in actionOutputs) { const output = actionOutputs[action]; const toolCallId = toolMap[action]; if (toolCallId !== undefined) { - // Add required output only - toolOutputs.push({ toolCallId, output }); + toolOutputs.push({ tool_call_id: toolCallId, output }); } } - // Submit the tool outputs - const run = await this._client.submitToolOutputsToRun( - assistants_state.thread_id!, - assistants_state.run_id!, - toolOutputs + const run = await this._client.beta.threads.runs.submitToolOutputsAndPoll( + assistantsState.thread_id!, + assistantsState.run_id!, + { tool_outputs: toolOutputs } ); - // Wait for the run to complete - const results = await this.waitForRun(assistants_state.thread_id!, run.id, true); + const results = await this.waitForRun(assistantsState.thread_id!, run.id, true); switch (results.status) { - case 'requires_action': + case 'requires_action': { state.setValue(SUBMIT_TOOL_OUTPUTS_VARIABLE, true); - return this.generatePlanFromTools(state, results.requiredAction!); - case 'completed': + return this.generatePlanFromTools(state, results.required_action!.submit_tool_outputs.tool_calls); + } + case 'completed': { state.setValue(SUBMIT_TOOL_OUTPUTS_VARIABLE, false); - return this.generatePlanFromMessages(assistants_state.thread_id!, assistants_state.last_message_id!); - case 'cancelled': + return this.generatePlanFromMessages(assistantsState.thread_id!, assistantsState.last_message_id!); + } + case 'cancelled': { return { type: 'plan', commands: [] }; - case 'expired': + } + case 'expired': { + const expiredCommand: PredictedDoCommand = { + type: 'DO', + action: AI.TooManyStepsActionName, + parameters: {} + }; return { type: 'plan', - commands: [{ type: 'DO', action: AI.TooManyStepsActionName } as PredictedDoCommand] + commands: [expiredCommand] }; + } default: throw new Error( - `Run failed ${results.status}. ErrorCode: ${results.lastError?.code}. ErrorMessage: ${results.lastError?.message}` + `Run failed ${results.status}. ErrorCode: ${results.last_error?.code}. ErrorMessage: ${results.last_error?.message}` ); } } /** * @private - * @param {TurnContext} context - The application Turn Context. - * @param {TurnState} state - The application Turn State. + * Submits user input to the assistant. + * @param {TurnContext} context - The turn context. + * @param {TState} state - The turn state. * @param {AI} ai - The AI instance. - * @returns {Promise} - The user's input as Plan. + * @returns {Promise} A plan based on the user input. */ private async submitUserInput(context: TurnContext, state: TState, ai: AI): Promise { - // Get the current thread_id - const thread_id = await this.ensureThreadCreated(state, context.activity.text); + const threadId = await this.ensureThreadCreated(state, context.activity.text); - // Add the users input to the thread - const message = await this._client.createMessage(thread_id, 'user', state.temp.input); + const message = await this._client.beta.threads.messages.create(threadId, { + role: 'user', + content: state.temp.input + }); - // Create a new run - const run = await this._client.createRun(thread_id, { - assistantId: this._options.assistant_id + const run = await this._client.beta.threads.runs.createAndPoll(threadId, { + assistant_id: this._options.assistant_id }); - // Update state and wait for the run to complete - this.updateAssistantsState(state, { thread_id, run_id: run.id, last_message_id: message.id ?? null }); - const results = await this.waitForRun(thread_id, run.id, true); + this.updateAssistantsState(state, { thread_id: threadId, run_id: run.id, last_message_id: message.id }); + const results = await this.waitForRun(threadId, run.id, true); switch (results.status) { case 'requires_action': state.setValue(SUBMIT_TOOL_OUTPUTS_VARIABLE, true); - return this.generatePlanFromTools(state, results.requiredAction!); - case 'completed': + return this.generatePlanFromTools(state, results.required_action!.submit_tool_outputs.tool_calls); + case 'completed': { state.setValue(SUBMIT_TOOL_OUTPUTS_VARIABLE, false); - return this.generatePlanFromMessages(thread_id, message.id!); - case 'cancelled': + return this.generatePlanFromMessages(threadId, message.id); + } + case 'cancelled': { return { type: 'plan', commands: [] }; - case 'expired': + } + case 'expired': { + const expiredCommand: PredictedDoCommand = { + type: 'DO', + action: AI.TooManyStepsActionName, + parameters: {} + }; return { type: 'plan', - commands: [{ type: 'DO', action: AI.TooManyStepsActionName } as PredictedDoCommand] + commands: [expiredCommand] }; + } default: throw new Error( - `Run failed ${results.status}. ErrorCode: ${results.lastError?.code}. ErrorMessage: ${results.lastError?.message}` + `Run failed ${results.status}. ErrorCode: ${results.last_error?.code}. ErrorMessage: ${results.last_error?.message}` ); } } /** * @private - * @param {TurnState} state - The application Turn State. - * @param {RequiredAction} required_action - The required action. - * @returns {Plan} - The generated Plan. + * Generates a plan from tool calls. + * @param {TState} state - The turn state. + * @param {OpenAI.Beta.Threads.Runs.RequiredActionFunctionToolCall[]} toolCalls - The tool calls to generate the plan from. + * @returns {Plan} A plan based on the tool calls. */ - private generatePlanFromTools(state: TState, required_action: RequiredAction): Plan { + private generatePlanFromTools( + state: TState, + toolCalls: OpenAI.Beta.Threads.Runs.RequiredActionFunctionToolCall[] + ): Plan { const plan: Plan = { type: 'plan', commands: [] }; - const tool_map: { [key: string]: string } = {}; - required_action.submitToolOutputs!.toolCalls.forEach((tool_call) => { - tool_map[tool_call.function.name] = tool_call.id; - plan.commands.push({ + const toolMap: { [key: string]: string } = {}; + toolCalls.forEach((toolCall) => { + toolMap[toolCall.function.name] = toolCall.id; + const doCommand: PredictedDoCommand = { type: 'DO', - action: tool_call.function.name, - parameters: JSON.parse(tool_call.function.arguments) - } as PredictedDoCommand); + action: toolCall.function.name, + parameters: JSON.parse(toolCall.function.arguments) + }; + plan.commands.push(doCommand); }); - state.setValue(SUBMIT_TOOL_OUTPUTS_MAP, tool_map); + state.setValue(SUBMIT_TOOL_OUTPUTS_MAP, toolMap); return plan; } /** * @private - * @param {string} thread_id - The current thread's ID. - * @param {string} last_message_id - ID of the last message. - * @returns {Promise} The generated Plan from messages. + * Generates a plan from messages. + * @param {string} thread_id - The ID of the thread. + * @param {string} last_message_id - The ID of the last message. + * @returns {Promise} A plan based on the messages. */ private async generatePlanFromMessages(thread_id: string, last_message_id: string): Promise { - // Find the new messages - const messages = await this._client.listMessages(thread_id); - const newMessages: ThreadMessage[] = []; - for (let i = 0; i < messages.data.length; i++) { - const message = messages.data[i]; - if (message.id == last_message_id) { - break; - } else { - newMessages.push(message); - } - } + const messages = await this._client.beta.threads.messages.list(thread_id); - // listMessages return messages in desc, reverse to be in asc order - newMessages.reverse(); + const lastMessageIndex = messages.data.findIndex((message) => message.id === last_message_id); + const newMessages = lastMessageIndex >= 0 ? messages.data.slice(0, lastMessageIndex) : []; - // Convert the messages to SAY commands const plan: Plan = { type: 'plan', commands: [] }; newMessages.forEach((message) => { message.content.forEach((content) => { - if (content.type == 'text') { - plan.commands.push({ + if (content.type === 'text') { + const sayCommand: PredictedSayCommand = { type: 'SAY', response: { role: 'assistant', content: content.text.value, context: { intent: '', - citations: content.text.annotations.map((annotation) => ({ - title: '', - url: '', - filepath: '', - content: annotation.text - })) + citations: + content.text.annotations?.map((annotation) => ({ + title: '', + url: '', + filepath: '', + content: annotation.text + })) || [] } } - } as PredictedSayCommand); + }; + plan.commands.push(sayCommand); } }); }); @@ -395,8 +388,9 @@ export class AssistantsPlanner implements /** * @private - * @param {TurnState} state - The application Turn State. - * @returns {AssistantsState} - The current Assistant's State. + * Ensures that the assistants state exists in the turn state. + * @param {TState} state - The turn state. + * @returns {AssistantsState} The assistants state. */ private ensureAssistantsState(state: TState): AssistantsState { if (!state.hasValue(this._options.assistants_state_variable!)) { @@ -411,52 +405,32 @@ export class AssistantsPlanner implements } /** - * @param {TurnState} state - The application Turn State. - * @param {AssistantsState} assistants_state - The Assistant's State. * @private + * Updates the assistants state in the turn state. + * @param {TState} state - The turn state. + * @param {AssistantsState} assistantsState - The new assistants state. */ - private updateAssistantsState(state: TState, assistants_state: AssistantsState): void { - state.setValue(this._options.assistants_state_variable!, assistants_state); + private updateAssistantsState(state: TState, assistantsState: AssistantsState): void { + state.setValue(this._options.assistants_state_variable!, assistantsState); } /** * @private - * @param {TurnState} state - The application Turn State. - * @param {string} input - The thread input. - * @returns {Promise} The created thread. - */ - private async ensureThreadCreated(state: TState, input: string): Promise { - const assistants_state = this.ensureAssistantsState(state); - if (assistants_state.thread_id == null) { - const thread = await this._client.createThread({}); - assistants_state.thread_id = thread.id; - this.updateAssistantsState(state, assistants_state); - } - - return assistants_state.thread_id; - } - - /** - * @private - * @param {string} thread_id - The id of the thread. - * @returns {Promise} A Promise. + * Blocks until all in-progress runs are completed. + * @param {string} thread_id - The ID of the thread. */ private async blockOnInProgressRuns(thread_id: string): Promise { - // We loop until we're told the last run is completed while (true) { - const runs = await this._client.listRuns(thread_id, { limit: 1 }); - if (runs.data.length == 0) { - return; - } - - const run = runs.data[0]; + const runs = await this._client.beta.threads.runs.list(thread_id, { limit: 1 }); - if (!run || this.isRunCompleted(run)) { - return; + if (runs.data.length === 0) { + return; // No runs, so we're done } - - // Wait for the current run to complete and then loop to see if there's already a new run. - await this.waitForRun(thread_id, run.id); + const latestRun = runs.data[0]; + if (this.isRunCompleted(latestRun)) { + return; // Latest run is completed, so we're done + } + await this.waitForRun(thread_id, latestRun.id); } } @@ -464,15 +438,16 @@ export class AssistantsPlanner implements * @private * @param {string} apiKey - The api key * @param {string} endpoint - The Azure OpenAI resource endpoint - * @returns {AssistantsClient} the client + * @param {Record} azureClientOptions - The Azure OpenAI client options. + * @returns {OpenAI} the client */ - private static createClient(apiKey: string, endpoint?: string): AssistantsClient { + private static createClient(apiKey: string, endpoint?: string, azureClientOptions?: AzureClientOptions): OpenAI { if (endpoint) { // Azure OpenAI - return new AssistantsClient(endpoint, new AzureKeyCredential(apiKey)); + return new AzureOpenAI({ endpoint, apiKey, ...azureClientOptions }); } else { // OpenAI - return new AssistantsClient(new OpenAIKeyCredential(apiKey)); + return new OpenAI({ apiKey }); } } } diff --git a/js/packages/teams-ai/src/types/ClientCitation.ts b/js/packages/teams-ai/src/types/ClientCitation.ts index cea3af835..cc42f1079 100644 --- a/js/packages/teams-ai/src/types/ClientCitation.ts +++ b/js/packages/teams-ai/src/types/ClientCitation.ts @@ -8,6 +8,29 @@ import { SensitivityUsageInfo } from './SensitivityUsageInfo'; +export type ClientCitationIconName = + | 'Microsoft Word' + | 'Microsoft Excel' + | 'Microsoft PowerPoint' + | 'Microsoft OneNote' + | 'Microsoft SharePoint' + | 'Microsoft Visio' + | 'Microsoft Loop' + | 'Microsoft Whiteboard' + | 'Adobe Illustrator' + | 'Adobe Photoshop' + | 'Adobe InDesign' + | 'Adobe Flash' + | 'Sketch' + | 'Source Code' + | 'Image' + | 'GIF' + | 'Video' + | 'Sound' + | 'ZIP' + | 'Text' + | 'PDF'; + /** * Represents a Teams client citation to be included in a message. See Bot messages with AI-generated content for more details. * https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bot-messages-ai-generated-content?tabs=before%2Cbotmessage @@ -21,7 +44,7 @@ export interface ClientCitation { /** * Required. Number and position of the citation. */ - position: string; + position: number; appearance: { /** * Required; Must be 'DigitalDocument' @@ -29,12 +52,13 @@ export interface ClientCitation { '@type': 'DigitalDocument'; /** - * Name of the document. + * Name of the document. (max length 80) */ name: string; /** - * Optional; ignored in Teams + * Stringified adaptive card with additional information about the citation. + * It is rendered within the modal. */ text?: string; @@ -44,22 +68,29 @@ export interface ClientCitation { url?: string; /** - * Content of the citation. Must be clipped if longer than 480 characters. + * Extract of the referenced content. (max length 160) */ abstract: string; /** - * Used for icon; for now it is ignored. + * Encoding format of the ` citation.appearance.text` field. */ - encodingFormat?: 'text/html'; + encodingFormat?: 'application/vnd.microsoft.card.adaptive'; /** - * For now ignored, later used for icon + * Information about the citation’s icon. */ - image?: string; + image?: { + '@type': 'ImageObject'; + + /** + * The image/icon name + */ + name: ClientCitationIconName; + }; /** - * Optional; set by developer + * Optional; set by developer. (max length 3) (max keyword length 28) */ keywords?: string[]; diff --git a/js/samples/03.ai-concepts/f.chatModeration/src/bot.ts b/js/samples/03.ai-concepts/f.chatModeration/src/bot.ts index beaa4e908..1c8244e75 100644 --- a/js/samples/03.ai-concepts/f.chatModeration/src/bot.ts +++ b/js/samples/03.ai-concepts/f.chatModeration/src/bot.ts @@ -68,7 +68,7 @@ if (process.env.OPENAI_KEY) { category: 'Violence', severity: ModerationSeverity.High } - ], + ] // haltOnBlocklistHit: true, // blocklistNames: [] // Text blocklist Name. Only support following characters: 0-9 A-Z a-z - . _ ~. You could attach multiple lists name here. }); diff --git a/js/samples/04.ai-apps/a.teamsChefBot/package.json b/js/samples/04.ai-apps/a.teamsChefBot/package.json index c7b46a423..3c6b1fb86 100644 --- a/js/samples/04.ai-apps/a.teamsChefBot/package.json +++ b/js/samples/04.ai-apps/a.teamsChefBot/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@microsoft/teams-ai": "~1.6.1", - "@microsoft/teams-js": "^2.30.0", + "@microsoft/teams-js": "^2.31.0", "botbuilder": "^4.23.1", "dotenv": "^16.4.5", "openai": "4.68.2", diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/.gitignore b/js/samples/04.ai-apps/e.assistants-orderBot/.gitignore index d35dca83b..6e9cf82bb 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/.gitignore +++ b/js/samples/04.ai-apps/e.assistants-orderBot/.gitignore @@ -112,3 +112,6 @@ env/.env.*.user env/.env.local appPackage/build .deployment +.localConfigs.testTool +.testTool.installInfo.json +teamsapptesttool.log diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/launch.json b/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/launch.json index 063ae2e52..958aba606 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/launch.json +++ b/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/launch.json @@ -28,9 +28,7 @@ "type": "msedge", "request": "launch", "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Attach to Local Service" - ], + "cascadeTerminateToConfigurations": ["Attach to Local Service"], "presentation": { "group": "all", "hidden": true @@ -42,9 +40,7 @@ "type": "chrome", "request": "launch", "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Attach to Local Service" - ], + "cascadeTerminateToConfigurations": ["Attach to Local Service"], "presentation": { "group": "all", "hidden": true @@ -67,10 +63,7 @@ "compounds": [ { "name": "Debug (Edge)", - "configurations": [ - "Launch App (Edge)", - "Attach to Local Service" - ], + "configurations": ["Launch App (Edge)", "Attach to Local Service"], "preLaunchTask": "Start Teams App Locally", "presentation": { "group": "all", @@ -80,16 +73,23 @@ }, { "name": "Debug (Chrome)", - "configurations": [ - "Launch App (Chrome)", - "Attach to Local Service" - ], + "configurations": ["Launch App (Chrome)", "Attach to Local Service"], "preLaunchTask": "Start Teams App Locally", "presentation": { "group": "all", "order": 2 }, "stopAll": true + }, + { + "name": "Debug in Test Tool", + "configurations": ["Attach to Local Service"], + "preLaunchTask": "Start Teams App (Test Tool)", + "presentation": { + "group": "local", + "order": 1 + }, + "stopAll": true } ] } diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/tasks.json b/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/tasks.json index 585f86ae9..f4729471a 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/tasks.json +++ b/js/samples/04.ai-apps/e.assistants-orderBot/.vscode/tasks.json @@ -6,13 +6,7 @@ "tasks": [ { "label": "Start Teams App Locally", - "dependsOn": [ - "Validate prerequisites", - "Start local tunnel", - "Provision", - "Deploy", - "Start application" - ], + "dependsOn": ["Validate prerequisites", "Start local tunnel", "Provision", "Deploy", "Start application"], "dependsOrder": "sequence" }, { @@ -100,6 +94,105 @@ "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" } } + }, + { + "label": "Start Teams App (Test Tool)", + "dependsOn": [ + "Validate prerequisites (Test Tool)", + "Deploy (Test Tool)", + "Start application (Test Tool)", + "Start Test Tool" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Test Tool)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150 // test tool port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Test Tool)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "testtool" + } + }, + { + "label": "Start application (Test Tool)", + "type": "shell", + "command": "npm run dev:teamsfx:testtool", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Test Tool", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } } ] -} \ No newline at end of file +} diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester b/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester new file mode 120000 index 000000000..e6800eee4 --- /dev/null +++ b/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester @@ -0,0 +1 @@ +/Users/corina/.fx/bin/testTool/0.2.4 \ No newline at end of file diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/env/.env.testtool b/js/samples/04.ai-apps/e.assistants-orderBot/env/.env.testtool new file mode 100644 index 000000000..3c22da192 --- /dev/null +++ b/js/samples/04.ai-apps/e.assistants-orderBot/env/.env.testtool @@ -0,0 +1,3 @@ +TEAMSFX_ENV=testtool +ASSISTANT_ID= +SECRET_OPENAI_KEY= \ No newline at end of file diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/package.json b/js/samples/04.ai-apps/e.assistants-orderBot/package.json index 7851e0922..719b24b44 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/package.json +++ b/js/samples/04.ai-apps/e.assistants-orderBot/package.json @@ -12,7 +12,9 @@ "start": "tsc --build && node ./lib/index.js", "test": "echo \"Error: no test specified\" && exit 1", "watch": "nodemon --watch ./src -e ts --exec \"yarn start\"", - "dev:teamsfx": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts" + "dev:teamsfx": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev:teamsfx", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start" }, "repository": { "type": "git", @@ -29,13 +31,13 @@ "restify": "~11.1.0" }, "devDependencies": { - "@types/node": "^20.16.1", "@types/jsonwebtoken": "^9.0.4", + "@types/node": "^20.16.1", "@types/restify": "8.5.12", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", - "eslint": "^8.57.1", "env-cmd": "^10.1.0", + "eslint": "^8.57.1", "nodemon": "~3.0.1", "prettier": "^3.3.3", "rimraf": "^5.0.10", diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/src/bot.ts b/js/samples/04.ai-apps/e.assistants-orderBot/src/bot.ts index 896f5d706..1121751e2 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/src/bot.ts +++ b/js/samples/04.ai-apps/e.assistants-orderBot/src/bot.ts @@ -1,4 +1,4 @@ -import { Application, preview, AI } from '@microsoft/teams-ai'; +import { Application, preview, AI, TurnState } from '@microsoft/teams-ai'; import { CardFactory, MemoryStorage, MessageFactory, TurnContext } from 'botbuilder'; import { Order } from './foodOrderViewSchema'; import { generateCardForOrder } from './foodOrderCard'; @@ -19,6 +19,7 @@ if (process.env.AZURE_OPENAI_KEY) { } const { AssistantsPlanner } = preview; +let assistantId = ''; // Create Assistant if no ID is provided, this will require you to restart the program and fill in the process.env.ASSISTANT_ID afterwards. if (!process.env.ASSISTANT_ID) { @@ -43,13 +44,16 @@ if (!process.env.ASSISTANT_ID) { } } ], - model: 'gpt-4' + model: 'gpt-4o-mini' }, - endpoint + endpoint ?? undefined, + endpoint ? { apiVersion: process.env.OPENAI_API_VERSION } : undefined ); console.log(`Created a new assistant with an ID of: ${assistant.id}`); - process.exit(); + console.log('Be sure to add this ID to your environment variables as ASSISTANT_ID before your next restart.'); + assistantId = assistant.id; + process.env.ASSISTANT_ID = assistantId; })(); } @@ -57,7 +61,7 @@ if (!process.env.ASSISTANT_ID) { const planner = new AssistantsPlanner({ apiKey: apiKey, endpoint: endpoint, - assistant_id: process.env.ASSISTANT_ID! + assistant_id: process.env.ASSISTANT_ID ?? assistantId }); // Define storage and application @@ -72,18 +76,18 @@ const app = new Application({ // Export bots run() function export const run = (context: TurnContext) => app.run(context); -app.message('/reset', async (context, state) => { +app.message('/reset', async (context: TurnContext, state: TurnState) => { state.deleteConversationState(); await context.sendActivity(`Ok lets start this over.`); }); -app.ai.action('place_order', async (context, state, order) => { +app.ai.action('place_order', async (context: TurnContext, state: TurnState, order: Order) => { const card = generateCardForOrder(order); await context.sendActivity(MessageFactory.attachment(CardFactory.adaptiveCard(card))); return `order placed`; }); -app.ai.action(AI.HttpErrorActionName, async (context, state, data) => { +app.ai.action(AI.HttpErrorActionName, async (context: TurnContext, state: TurnState, _data: unknown) => { await context.sendActivity('An AI request failed. Please try again later.'); return AI.StopCommandName; }); diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/src/index.ts b/js/samples/04.ai-apps/e.assistants-orderBot/src/index.ts index 9a37c8d2f..046b47528 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/src/index.ts +++ b/js/samples/04.ai-apps/e.assistants-orderBot/src/index.ts @@ -68,7 +68,7 @@ server.listen(process.env.port || process.env.PORT || 3978, () => { // Listen for incoming server requests. server.post('/api/messages', async (req, res) => { // Route received a request to adapter for processing - await adapter.process(req, res as any, async (context) => { + await adapter.process(req, res as any, async (context: TurnContext) => { // Dispatch to application for routing await bot.run(context); }); diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.testtool.yml b/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.testtool.yml new file mode 100644 index 000000000..374c8adac --- /dev/null +++ b/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.testtool.yml @@ -0,0 +1,27 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.5 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1 + symlinkDir: ./devTools/teamsapptester + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install --no-audit --workspaces=false + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.testTool + envs: + OPENAI_KEY: ${{SECRET_OPENAI_KEY}} + ASSISTANT_ID: ${{ASSISTANT_ID}} + # AZURE_OPENAI_KEY: ${{SECRET_AZURE_OPENAI_KEY}} + # AZURE_OPENAI_ENDPOINT: ${{SECRET_AZURE_OPENAI_ENDPOINT}} diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.yml b/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.yml index f44ec818a..0dd6c5703 100644 --- a/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.yml +++ b/js/samples/04.ai-apps/e.assistants-orderBot/teamsapp.yml @@ -13,102 +13,103 @@ 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: OrderBot${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - teamsAppId: TEAMS_APP_ID + # Automates the creation of a Teams app registration and saves the App ID to an environment file. + - uses: teamsApp/create + with: + name: OrderBot${{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: OrderBot${{APP_NAME_SUFFIX}} - writeToEnvironmentFile: - botId: BOT_ID - botPassword: SECRET_BOT_PASSWORD + # 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: OrderBot${{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-for-bot - bicepCliVersion: v0.9.1 + # 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-for-bot + 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 + # 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 + # 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 + # 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 + # 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 or CLI using `teamsfx deploy --env {environment name}`. deploy: - # Install any dependencies and build the web app using NPM - - uses: cli/runNpmCommand - name: install dependencies - with: - args: install + # Install any dependencies and build the web app using NPM + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install - - uses: cli/runNpmCommand - name: build app - with: - args: run build --if-present - # Deploy to an Azure App Service using the zip file created in the provision step. - - uses: azureAppService/zipDeploy - with: - artifactFolder: . - ignoreFile: .webappignore - # This example uses the env var thats generated by the arm/deploy action. - # You can replace it with an existing Azure Resource ID or other - # custom environment variable. - resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy to an Azure App Service using the zip file created in the provision step. + - uses: azureAppService/zipDeploy + with: + artifactFolder: . + ignoreFile: .webappignore + # This example uses the env var thats generated by the arm/deploy action. + # You can replace it with an existing Azure Resource ID or other + # custom environment variable. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} # Defines what the `publish` lifecycle step does with Teams Toolkit. # Runs with the Deploy menu or CLI using `teamsfx publish --env {environment name}`. publish: - # 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 + # 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 + # 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 + # 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 + # 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 +projectId: bb70f06c-3c60-49ad-b6eb-f20acd222890 diff --git a/js/samples/04.ai-apps/f.whoBot/package.json b/js/samples/04.ai-apps/f.whoBot/package.json index a702c65e0..1393bb60c 100644 --- a/js/samples/04.ai-apps/f.whoBot/package.json +++ b/js/samples/04.ai-apps/f.whoBot/package.json @@ -38,7 +38,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/samples/04.ai-apps/h.datasource-azureOpenAI/src/index.ts b/js/samples/04.ai-apps/h.datasource-azureOpenAI/src/index.ts index 1c08ccb24..192bfb869 100644 --- a/js/samples/04.ai-apps/h.datasource-azureOpenAI/src/index.ts +++ b/js/samples/04.ai-apps/h.datasource-azureOpenAI/src/index.ts @@ -8,7 +8,7 @@ import debug from 'debug'; import * as restify from 'restify'; import { app } from './app'; import { TeamsAdapter } from '@microsoft/teams-ai'; -import { Request, Response, TurnContext } from 'botbuilder'; +import { TurnContext } from 'botbuilder'; const ENV_FILE = path.join(__dirname, '..', '.env'); config({ path: ENV_FILE }); diff --git a/js/samples/04.ai-apps/i.teamsChefBot-streaming/package.json b/js/samples/04.ai-apps/i.teamsChefBot-streaming/package.json index 5fb31f7bf..27ba685c4 100644 --- a/js/samples/04.ai-apps/i.teamsChefBot-streaming/package.json +++ b/js/samples/04.ai-apps/i.teamsChefBot-streaming/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@microsoft/teams-ai": "~1.6.1", - "@microsoft/teams-js": "^2.30.0", + "@microsoft/teams-js": "^2.31.0", "botbuilder": "^4.23.1", "dotenv": "^16.4.5", "openai": "4.41.1", diff --git a/js/samples/04.ai-apps/i.teamsChefBot-streaming/src/index.ts b/js/samples/04.ai-apps/i.teamsChefBot-streaming/src/index.ts index 7ef8ff0f4..0139657e4 100644 --- a/js/samples/04.ai-apps/i.teamsChefBot-streaming/src/index.ts +++ b/js/samples/04.ai-apps/i.teamsChefBot-streaming/src/index.ts @@ -142,8 +142,8 @@ const app = new Application({ storage, ai: { planner, - enable_feedback_loop: true, - }, + enable_feedback_loop: true + } }); // Register your data source with planner @@ -175,8 +175,8 @@ app.ai.action(AI.FlaggedOutputActionName, async (context: TurnContext, state: Ap }); app.feedbackLoop(async (context, state, feedbackLoopData) => { - console.log("Feedback loop triggered"); - }); + console.log('Feedback loop triggered'); +}); // Listen for incoming server requests. server.post('/api/messages', async (req, res) => { diff --git a/js/samples/05.authentication/a.oauth-adaptiveCard/package.json b/js/samples/05.authentication/a.oauth-adaptiveCard/package.json index c95229f6e..fe3555f4a 100644 --- a/js/samples/05.authentication/a.oauth-adaptiveCard/package.json +++ b/js/samples/05.authentication/a.oauth-adaptiveCard/package.json @@ -39,7 +39,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/samples/05.authentication/b.oauth-bot/package.json b/js/samples/05.authentication/b.oauth-bot/package.json index 63e37c1dd..fc5a97d5f 100644 --- a/js/samples/05.authentication/b.oauth-bot/package.json +++ b/js/samples/05.authentication/b.oauth-bot/package.json @@ -37,7 +37,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/samples/05.authentication/c.oauth-messageExtension/package.json b/js/samples/05.authentication/c.oauth-messageExtension/package.json index 7ca2df4eb..4bf51c68c 100644 --- a/js/samples/05.authentication/c.oauth-messageExtension/package.json +++ b/js/samples/05.authentication/c.oauth-messageExtension/package.json @@ -40,7 +40,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/samples/05.authentication/d.teamsSSO-bot/package.json b/js/samples/05.authentication/d.teamsSSO-bot/package.json index 9201182ae..076668c01 100644 --- a/js/samples/05.authentication/d.teamsSSO-bot/package.json +++ b/js/samples/05.authentication/d.teamsSSO-bot/package.json @@ -39,7 +39,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/samples/05.authentication/e.teamsSSO-messageExtension/package.json b/js/samples/05.authentication/e.teamsSSO-messageExtension/package.json index d9aa7a68c..e90f21381 100644 --- a/js/samples/05.authentication/e.teamsSSO-messageExtension/package.json +++ b/js/samples/05.authentication/e.teamsSSO-messageExtension/package.json @@ -42,7 +42,7 @@ "prettier": "^3.3.3", "rimraf": "^5.0.10", "ts-node": "^10.9.2", - "tsc-watch": "^6.2.0", + "tsc-watch": "^6.2.1", "typescript": "~5.5.4" } } diff --git a/js/yarn.lock b/js/yarn.lock index 9d6b1d5ec..3163d7b4c 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -10,18 +10,6 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@azure-rest/core-client@^1.1.4": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@azure-rest/core-client/-/core-client-1.4.0.tgz#3be28c02c6c20e49dea73a7f012daeeda4eacb8e" - integrity sha512-ozTDPBVUDR5eOnMIwhggbnVmOrka4fXCs8n8mvUo4WLLc38kki6bAOByDoVZZPz/pZy2jMt2kwfpvy/UjALj6w== - dependencies: - "@azure/abort-controller" "^2.0.0" - "@azure/core-auth" "^1.3.0" - "@azure/core-rest-pipeline" "^1.5.0" - "@azure/core-tracing" "^1.0.1" - "@azure/core-util" "^1.0.0" - tslib "^2.6.2" - "@azure/abort-controller@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" @@ -29,7 +17,7 @@ dependencies: tslib "^2.2.0" -"@azure/abort-controller@^2.0.0": +"@azure/abort-controller@^2.0.0", "@azure/abort-controller@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-2.1.2.tgz#42fe0ccab23841d9905812c58f1082d27784566d" integrity sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA== @@ -45,7 +33,7 @@ "@azure/core-util" "^1.1.0" tslib "^2.6.2" -"@azure/core-auth@^1.1.4", "@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0", "@azure/core-auth@^1.5.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": +"@azure/core-auth@^1.1.4", "@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.9.0.tgz#ac725b03fabe3c892371065ee9e2041bee0fd1ac" integrity sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw== @@ -54,7 +42,7 @@ "@azure/core-util" "^1.11.0" tslib "^2.6.2" -"@azure/core-client@^1.3.0", "@azure/core-client@^1.6.2", "@azure/core-client@^1.7.3", "@azure/core-client@^1.9.2": +"@azure/core-client@^1.3.0", "@azure/core-client@^1.6.2", "@azure/core-client@^1.9.2": version "1.9.2" resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74" integrity sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w== @@ -77,9 +65,9 @@ "@azure/core-rest-pipeline" "^1.3.0" "@azure/core-http@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-3.0.4.tgz#024b2909bbc0f2fce08c74f97a21312c4f42e922" - integrity sha512-Fok9VVhMdxAFOtqiiAtg74fL0UJkt0z3D+ouUUxcRLzZNBioPRAMJFVxiWoJljYpXsRi4GDQHzQHDc9AiYaIUQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-3.0.5.tgz#727fc23bfda67cd10d0eaf3a30cfb3a32f8fb6a0" + integrity sha512-T8r2q/c3DxNu6mEJfPuJtptUVqwchxzjj32gKcnMi06rdiVONS9rar7kT9T2Am+XvER7uOzpsP79WsqNbdgdWg== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-auth" "^1.3.0" @@ -127,15 +115,15 @@ https-proxy-agent "^7.0.0" tslib "^2.6.2" -"@azure/core-rest-pipeline@^1.10.1", "@azure/core-rest-pipeline@^1.13.0", "@azure/core-rest-pipeline@^1.17.0", "@azure/core-rest-pipeline@^1.3.0", "@azure/core-rest-pipeline@^1.5.0", "@azure/core-rest-pipeline@^1.9.1": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz#55dafa1093553c549ed6d8dbca69aa505c7b3aa3" - integrity sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA== +"@azure/core-rest-pipeline@^1.10.1", "@azure/core-rest-pipeline@^1.17.0", "@azure/core-rest-pipeline@^1.3.0", "@azure/core-rest-pipeline@^1.9.1": + version "1.18.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.1.tgz#380e7d3f15be80de83ee414176adb32824402f38" + integrity sha512-/wS73UEDrxroUEVywEm7J0p2c+IIiVxyfigCGfsKvCxxCET4V/Hef2aURqltrXMRjNmdmt5IuOgIpl8f6xdO5A== dependencies: "@azure/abort-controller" "^2.0.0" "@azure/core-auth" "^1.8.0" "@azure/core-tracing" "^1.0.1" - "@azure/core-util" "^1.9.0" + "@azure/core-util" "^1.11.0" "@azure/logger" "^1.0.0" http-proxy-agent "^7.0.0" https-proxy-agent "^7.0.0" @@ -149,10 +137,10 @@ "@opentelemetry/api" "^1.0.1" tslib "^2.2.0" -"@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1", "@azure/core-tracing@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.1.2.tgz#065dab4e093fb61899988a1cdbc827d9ad90b4ee" - integrity sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA== +"@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1", "@azure/core-tracing@^1.1.2", "@azure/core-tracing@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.2.0.tgz#7be5d53c3522d639cf19042cbcdb19f71bc35ab2" + integrity sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg== dependencies: tslib "^2.6.2" @@ -164,12 +152,12 @@ "@azure/abort-controller" "^2.0.0" tslib "^2.6.2" -"@azure/core-xml@^1.3.2": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@azure/core-xml/-/core-xml-1.4.3.tgz#a74f37a0e584fee7e9adae19f51016d4b59e9ca2" - integrity sha512-D6G7FEmDiTctPKuWegX2WTrS1enKZwqYwdKTO6ZN6JMigcCehlT0/CYl+zWpI9vQ9frwwp7GQT3/owaEXgnOsA== +"@azure/core-xml@^1.4.3": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@azure/core-xml/-/core-xml-1.4.4.tgz#a8656751943bf492762f758d147d33dfcd933d9e" + integrity sha512-J4FYAqakGXcbfeZjwjMzjNcpcH4E+JtEBv+xcV1yL0Ydn/6wbQfeFKTCHh9wttAi0lmajHw7yBbHPRG+YHckZQ== dependencies: - fast-xml-parser "^4.3.2" + fast-xml-parser "^4.4.1" tslib "^2.6.2" "@azure/identity@^4.4.1", "@azure/identity@^4.5.0": @@ -192,7 +180,7 @@ stoppable "^1.1.0" tslib "^2.2.0" -"@azure/logger@^1.0.0", "@azure/logger@^1.0.4", "@azure/logger@^1.1.4": +"@azure/logger@^1.0.0", "@azure/logger@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" integrity sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ== @@ -214,50 +202,37 @@ xml2js "^0.5.0" "@azure/msal-browser@^3.26.1": - version "3.26.1" - resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.26.1.tgz#2f4368d7997682db30dca52e32fcac363fa0efad" - integrity sha512-y78sr9g61aCAH9fcLO1um+oHFXc1/5Ap88RIsUSuzkm0BHzFnN+PXGaQeuM1h5Qf5dTnWNOd6JqkskkMPAhh7Q== + version "3.27.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.27.0.tgz#b6f02f73c8e102d3f115009b4677539fb173fe2b" + integrity sha512-+b4ZKSD8+vslCtVRVetkegEhOFMLP3rxDWJY212ct+2r6jVg6OSQKc1Qz3kCoXo0FgwaXkb+76TMZfpHp8QtgA== dependencies: - "@azure/msal-common" "14.15.0" + "@azure/msal-common" "14.16.0" -"@azure/msal-common@14.15.0": - version "14.15.0" - resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55" - integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ== +"@azure/msal-common@14.16.0": + version "14.16.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.16.0.tgz#f3470fcaec788dbe50859952cd499340bda23d7a" + integrity sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA== -"@azure/msal-node@^2.13.1", "@azure/msal-node@^2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.15.0.tgz#50bf8e692a6656027c073a75d877a8a478aafdfd" - integrity sha512-gVPW8YLz92ZeCibQH2QUw96odJoiM3k/ZPH3f2HxptozmH6+OnyyvKXo/Egg39HAM230akarQKHf0W74UHlh0Q== +"@azure/msal-node@^2.13.1", "@azure/msal-node@^2.15.0", "@azure/msal-node@^2.16.1": + version "2.16.2" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.16.2.tgz#3eb768d36883ea6f9a939c0b5b467b518e78fffc" + integrity sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ== dependencies: - "@azure/msal-common" "14.15.0" + "@azure/msal-common" "14.16.0" jsonwebtoken "^9.0.0" uuid "^8.3.0" -"@azure/openai-assistants@1.0.0-beta.6": - version "1.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@azure/openai-assistants/-/openai-assistants-1.0.0-beta.6.tgz#8937267f631b257d099aaa17776b324a935b15d9" - integrity sha512-gINKKcqTpR0neF+36Owe0Q1u1JO3IK6clBzWTfZ+9V/TkQq+LoUgp5F8dKvSv/YChfwEpZA2r1DWCwNE07eYIQ== - dependencies: - "@azure-rest/core-client" "^1.1.4" - "@azure/core-auth" "^1.5.0" - "@azure/core-client" "^1.7.3" - "@azure/core-rest-pipeline" "^1.13.0" - "@azure/core-util" "^1.6.1" - "@azure/logger" "^1.0.4" - tslib "^2.2.0" - "@azure/opentelemetry-instrumentation-azure-sdk@^1.0.0-beta.5": - version "1.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.6.tgz#94f46c3ccffa7e05f1776a137327fda27220d240" - integrity sha512-JP6TJ7vDNX6r0gN2+EQBINTNqZ86frl1RAj5STtbLP1ClgIhcdXXb0hvq7CuEOv7InrroHMDoEYG80OQcWChug== + version "1.0.0-beta.7" + resolved "https://registry.yarnpkg.com/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.7.tgz#db55c80a7778371312f8ff95a7b854a14e88dd76" + integrity sha512-boG33EDRcbw0Jo2cRgB6bccSirKOzYdYFMdcSsnOajLCLfJ8WIve3vxUMi7YZKxM8txZX/0cwzUU6crXmYxXZg== dependencies: - "@azure/core-tracing" "^1.0.0" + "@azure/core-tracing" "^1.2.0" "@azure/logger" "^1.0.0" "@opentelemetry/api" "^1.9.0" - "@opentelemetry/core" "^1.25.1" - "@opentelemetry/instrumentation" "^0.52.1" - tslib "^2.2.0" + "@opentelemetry/core" "^1.26.0" + "@opentelemetry/instrumentation" "^0.53.0" + tslib "^2.7.0" "@azure/search-documents@12.1.0": version "12.1.0" @@ -276,11 +251,11 @@ tslib "^2.2.0" "@azure/storage-blob@^12.24.0": - version "12.24.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.24.0.tgz#d4ae1e29574b4a19d90eaf082cfde95f996d3f9b" - integrity sha512-l8cmWM4C7RoNCBOImoFMxhTXe1Lr+8uQ/IgnhRNMpfoA9bAFWoLG4XrWm6O5rKXortreVQuD+fc1hbzWklOZbw== + version "12.26.0" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.26.0.tgz#1fae3a0b68d8a91b56c89f353507916bf8705d1c" + integrity sha512-SriLPKezypIsiZ+TtlFfE46uuBIap2HeaQVS78e1P7rz5OSbq0rsd52WE1mC5f7vAeLiXqv7I7oRhL3WFZEw3Q== dependencies: - "@azure/abort-controller" "^1.0.0" + "@azure/abort-controller" "^2.1.2" "@azure/core-auth" "^1.4.0" "@azure/core-client" "^1.6.2" "@azure/core-http-compat" "^2.0.0" @@ -289,169 +264,151 @@ "@azure/core-rest-pipeline" "^1.10.1" "@azure/core-tracing" "^1.1.2" "@azure/core-util" "^1.6.1" - "@azure/core-xml" "^1.3.2" + "@azure/core-xml" "^1.4.3" "@azure/logger" "^1.0.0" events "^3.0.0" tslib "^2.2.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/highlight" "^7.24.7" + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/compat-data@^7.25.2": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" - integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== +"@babel/compat-data@^7.25.9": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" + integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== "@babel/core@^7.7.5": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" - integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/helper-compilation-targets" "^7.25.2" - "@babel/helper-module-transforms" "^7.25.2" - "@babel/helpers" "^7.25.0" - "@babel/parser" "^7.25.0" - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.2" - "@babel/types" "^7.25.2" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.0", "@babel/generator@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" - integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== dependencies: - "@babel/types" "^7.25.6" + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" + jsesc "^3.0.2" -"@babel/helper-compilation-targets@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" - integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== dependencies: - "@babel/compat-data" "^7.25.2" - "@babel/helper-validator-option" "^7.24.8" - browserslist "^4.23.1" + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-module-imports@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" - integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-module-transforms@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" - integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== - dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.2" - -"@babel/helper-simple-access@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" - integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-string-parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" - integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/helper-validator-option@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" - integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== - -"@babel/helpers@^7.25.0": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" - integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== - dependencies: - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" - -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" -"@babel/parser@^7.23.0", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3", "@babel/parser@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" - integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== dependencies: - "@babel/types" "^7.25.6" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + +"@babel/parser@^7.23.0", "@babel/parser@^7.25.3", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== + dependencies: + "@babel/types" "^7.26.0" "@babel/runtime@^7.12.5": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" - integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" - integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.25.0" - "@babel/types" "^7.25.0" - -"@babel/traverse@^7.23.2", "@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" - integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.6" - "@babel/parser" "^7.25.6" - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.23.2", "@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" - integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== +"@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" "@browserify/envify@^6.0.0": version "6.0.0" @@ -491,16 +448,16 @@ jsdoc-type-pratt-parser "~4.0.0" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== dependencies: - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" - integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -635,27 +592,27 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@microsoft/api-extractor-model@7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.29.8.tgz#fa6d0c48374f1105c0637f0882cfe2044d88520a" - integrity sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g== +"@microsoft/api-extractor-model@7.30.0": + version "7.30.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.30.0.tgz#18a0528350124015b2c08397474e9309a8b3c807" + integrity sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug== dependencies: - "@microsoft/tsdoc" "~0.15.0" - "@microsoft/tsdoc-config" "~0.17.0" - "@rushstack/node-core-library" "5.9.0" + "@microsoft/tsdoc" "~0.15.1" + "@microsoft/tsdoc-config" "~0.17.1" + "@rushstack/node-core-library" "5.10.0" "@microsoft/api-extractor@^7.47.11": - version "7.47.11" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.47.11.tgz#00450fb4f4c30f76c18d36110aa4cae1cdc2191c" - integrity sha512-lrudfbPub5wzBhymfFtgZKuBvXxoSIAdrvS2UbHjoMT2TjIEddq6Z13pcve7A03BAouw0x8sW8G4txdgfiSwpQ== - dependencies: - "@microsoft/api-extractor-model" "7.29.8" - "@microsoft/tsdoc" "~0.15.0" - "@microsoft/tsdoc-config" "~0.17.0" - "@rushstack/node-core-library" "5.9.0" + version "7.48.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.48.0.tgz#d87243bdafbfadcf87b336b2b4e5de71ecc7caab" + integrity sha512-FMFgPjoilMUWeZXqYRlJ3gCVRhB7WU/HN88n8OLqEsmsG4zBdX/KQdtJfhq95LQTQ++zfu0Em1LLb73NqRCLYQ== + dependencies: + "@microsoft/api-extractor-model" "7.30.0" + "@microsoft/tsdoc" "~0.15.1" + "@microsoft/tsdoc-config" "~0.17.1" + "@rushstack/node-core-library" "5.10.0" "@rushstack/rig-package" "0.5.3" - "@rushstack/terminal" "0.14.2" - "@rushstack/ts-command-line" "4.23.0" + "@rushstack/terminal" "0.14.3" + "@rushstack/ts-command-line" "4.23.1" lodash "~4.17.15" minimatch "~3.0.3" resolve "~1.22.1" @@ -756,28 +713,28 @@ resolved "https://registry.yarnpkg.com/@microsoft/recognizers-text/-/recognizers-text-1.3.1.tgz#eda98a9148101ecdb04ed1424082d472b04aabd9" integrity sha512-HikLoRUgSzM4OKP3JVBzUUp3Q7L4wgI17p/3rERF01HVmopcujY3i6wgx8PenCwbenyTNxjr1AwSDSVuFlYedQ== -"@microsoft/teams-js@^2.30.0": - version "2.30.0" - resolved "https://registry.yarnpkg.com/@microsoft/teams-js/-/teams-js-2.30.0.tgz#26f91ae6072425d9c86cac841ebc3458f1fa7f31" - integrity sha512-zTy2EHuODZ2g4fXLvnKXdZse44n06iXGambmJnGDDCb6JRvG5dQwJp//swLoxBYs2iHL40TJdyUDoB6sTl6KXg== +"@microsoft/teams-js@^2.31.0": + version "2.31.1" + resolved "https://registry.yarnpkg.com/@microsoft/teams-js/-/teams-js-2.31.1.tgz#6f3989613ccf8b81efb983c48dda3ffc1ae15431" + integrity sha512-HzUYELJxukST2lG3dlTOjSZNg9G+6JeWcXF6gEJU6OEFpdWN2EKlUBw2q4VLQFwwHV2I9+eCOALOEHFhGtEQxQ== dependencies: base64-js "^1.3.1" debug "^4.3.3" -"@microsoft/tsdoc-config@~0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz#82605152b3c1d3f5cd4a11697bc298437484d55d" - integrity sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg== +"@microsoft/tsdoc-config@~0.17.1": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz#e0f0b50628f4ad7fe121ca616beacfe6a25b9335" + integrity sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw== dependencies: - "@microsoft/tsdoc" "0.15.0" + "@microsoft/tsdoc" "0.15.1" ajv "~8.12.0" jju "~1.4.0" resolve "~1.22.2" -"@microsoft/tsdoc@0.15.0", "@microsoft/tsdoc@~0.15.0": - version "0.15.0" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d" - integrity sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA== +"@microsoft/tsdoc@0.15.1", "@microsoft/tsdoc@~0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2" + integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== "@mixmark-io/domino@^2.2.0": version "2.2.0" @@ -814,10 +771,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@opentelemetry/api-logs@0.52.1": - version "0.52.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" - integrity sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A== +"@opentelemetry/api-logs@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz#c478cbd8120ec2547b64edfa03a552cfe42170be" + integrity sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw== dependencies: "@opentelemetry/api" "^1.0.0" @@ -826,47 +783,52 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/core@1.26.0", "@opentelemetry/core@^1.19.0", "@opentelemetry/core@^1.25.1": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" - integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== +"@opentelemetry/core@1.28.0", "@opentelemetry/core@^1.19.0", "@opentelemetry/core@^1.26.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.28.0.tgz#e97290a3e36c59480ffb2287fe2713c66749274c" + integrity sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw== dependencies: "@opentelemetry/semantic-conventions" "1.27.0" -"@opentelemetry/instrumentation@^0.52.1": - version "0.52.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz#2e7e46a38bd7afbf03cf688c862b0b43418b7f48" - integrity sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw== +"@opentelemetry/instrumentation@^0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz#e6369e4015eb5112468a4d45d38dcada7dad892d" + integrity sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A== dependencies: - "@opentelemetry/api-logs" "0.52.1" - "@types/shimmer" "^1.0.2" + "@opentelemetry/api-logs" "0.53.0" + "@types/shimmer" "^1.2.0" import-in-the-middle "^1.8.1" require-in-the-middle "^7.1.1" semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/resources@1.26.0": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" - integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== +"@opentelemetry/resources@1.28.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.28.0.tgz#c8c27ae7559c817f9d117f1bf96d76f893fb29f5" + integrity sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw== dependencies: - "@opentelemetry/core" "1.26.0" + "@opentelemetry/core" "1.28.0" "@opentelemetry/semantic-conventions" "1.27.0" "@opentelemetry/sdk-trace-base@^1.19.0": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" - integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + version "1.28.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.28.0.tgz#6195dc8cd78bd74394cf54c67c5cbd8d1528516c" + integrity sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA== dependencies: - "@opentelemetry/core" "1.26.0" - "@opentelemetry/resources" "1.26.0" + "@opentelemetry/core" "1.28.0" + "@opentelemetry/resources" "1.28.0" "@opentelemetry/semantic-conventions" "1.27.0" -"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.19.0": +"@opentelemetry/semantic-conventions@1.27.0": version "1.27.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== +"@opentelemetry/semantic-conventions@^1.19.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" + integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -882,10 +844,10 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@rushstack/node-core-library@5.9.0": - version "5.9.0" - resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.9.0.tgz#097213d518b29a9c28b46db9c2cc7968c67168f8" - integrity sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg== +"@rushstack/node-core-library@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-5.10.0.tgz#84173c913761a7d1edef5c818ce03d9e22cab9d7" + integrity sha512-2pPLCuS/3x7DCd7liZkqOewGM0OzLyCacdvOe8j6Yrx9LkETGnxul1t7603bIaB8nUAooORcct9fFDOQMbWAgw== dependencies: ajv "~8.13.0" ajv-draft-04 "~1.0.0" @@ -904,46 +866,39 @@ resolve "~1.22.1" strip-json-comments "~3.1.1" -"@rushstack/terminal@0.14.2": - version "0.14.2" - resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.14.2.tgz#cc34654990500e9413265e9fc03839fa3419c4c9" - integrity sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg== +"@rushstack/terminal@0.14.3": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.14.3.tgz#eae0198e73eac56c901f6e00d0d4254c50a3f655" + integrity sha512-csXbZsAdab/v8DbU1sz7WC2aNaKArcdS/FPmXMOXEj/JBBZMvDK0+1b4Qao0kkG0ciB1Qe86/Mb68GjH6/TnMw== dependencies: - "@rushstack/node-core-library" "5.9.0" + "@rushstack/node-core-library" "5.10.0" supports-color "~8.1.1" -"@rushstack/ts-command-line@4.23.0": - version "4.23.0" - resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.23.0.tgz#d2517f9da17a2f7b8967cdb417c39edc25b432ba" - integrity sha512-jYREBtsxduPV6ptNq8jOKp9+yx0ld1Tb/Tkdnlj8gTjazl1sF3DwX2VbluyYrNd0meWIL0bNeer7WDf5tKFjaQ== +"@rushstack/ts-command-line@4.23.1": + version "4.23.1" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.23.1.tgz#d5e33dbb1a016d9440b3a20010b82ccfe9abd34a" + integrity sha512-40jTmYoiu/xlIpkkRsVfENtBq4CW3R4azbL0Vmda+fMwHWqss6wwf/Cy/UJmMqIzpfYc2OTnjYP1ZLD3CmyeCA== dependencies: - "@rushstack/terminal" "0.14.2" + "@rushstack/terminal" "0.14.3" "@types/argparse" "1.0.38" argparse "~1.0.9" string-argv "~0.3.1" -"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": +"@sinonjs/commons@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/fake-timers@^11.2.2": - version "11.3.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz#51d6e8d83ca261ff02c0ab0e68e9db23d5cd5999" - integrity sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA== +"@sinonjs/fake-timers@^13.0.1", "@sinonjs/fake-timers@^13.0.2": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== dependencies: "@sinonjs/commons" "^3.0.1" -"@sinonjs/samsam@^8.0.0": +"@sinonjs/samsam@^8.0.1": version "8.0.2" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== @@ -952,7 +907,7 @@ lodash.get "^4.4.2" type-detect "^4.1.0" -"@sinonjs/text-encoding@^0.7.2": +"@sinonjs/text-encoding@^0.7.3": version "0.7.3" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== @@ -1022,9 +977,9 @@ "@types/ms" "*" "@types/express-serve-static-core@^4.17.33": - version "4.19.5" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" - integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + version "4.19.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" + integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== dependencies: "@types/node" "*" "@types/qs" "*" @@ -1095,7 +1050,7 @@ dependencies: "@types/node" "*" -"@types/jsonwebtoken@9.0.6", "@types/jsonwebtoken@^9.0.4", "@types/jsonwebtoken@^9.0.6": +"@types/jsonwebtoken@9.0.6": version "9.0.6" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3" integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== @@ -1109,10 +1064,17 @@ dependencies: "@types/node" "*" -"@types/lodash@^4.17.12": - version "4.17.12" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.12.tgz#25d71312bf66512105d71e55d42e22c36bcfc689" - integrity sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ== +"@types/jsonwebtoken@^9.0.4", "@types/jsonwebtoken@^9.0.6": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" + integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + dependencies: + "@types/node" "*" + +"@types/lodash@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb" + integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg== "@types/mime@^1": version "1.3.5" @@ -1125,9 +1087,9 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/mocha@^10.0.9": - version "10.0.9" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.9.tgz#101e9da88d2c02e5ac8952982c23b224524d662a" - integrity sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q== + version "10.0.10" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" + integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== "@types/ms@*": version "0.7.34" @@ -1135,19 +1097,19 @@ integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.6.4": - version "2.6.11" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" - integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== dependencies: "@types/node" "*" form-data "^4.0.0" "@types/node@*": - version "22.5.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" - integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== + version "22.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766" + integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== dependencies: - undici-types "~6.19.2" + undici-types "~6.20.0" "@types/node@18.19.47": version "18.19.47" @@ -1157,16 +1119,16 @@ undici-types "~5.26.4" "@types/node@^18.11.18": - version "18.19.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.50.tgz#8652b34ee7c0e7e2004b3f08192281808d41bf5a" - integrity sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg== + version "18.19.67" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.67.tgz#77c4b01641a1e3e1509aff7e10d39e4afd5ae06d" + integrity sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ== dependencies: undici-types "~5.26.4" "@types/node@^20.16.1": - version "20.16.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.5.tgz#d43c7f973b32ffdf9aa7bd4f80e1072310fd7a53" - integrity sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA== + version "20.17.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.9.tgz#5f141d4b7ee125cdee5faefe28de095398865bab" + integrity sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw== dependencies: undici-types "~6.19.2" @@ -1176,9 +1138,9 @@ integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== "@types/qs@*": - version "6.9.15" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" - integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + version "6.9.17" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a" + integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ== "@types/range-parser@*": version "1.2.7" @@ -1217,15 +1179,15 @@ "@types/node" "*" "@types/send" "*" -"@types/shimmer@^1.0.2": +"@types/shimmer@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.2.0.tgz#9b706af96fa06416828842397a70dfbbf1c14ded" integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== -"@types/sinon@^10.0.19": - version "10.0.20" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.20.tgz#f1585debf4c0d99f9938f4111e5479fb74865146" - integrity sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg== +"@types/sinon@^17.0.3": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" + integrity sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw== dependencies: "@types/sinonjs__fake-timers" "*" @@ -1363,52 +1325,52 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vue/compiler-core@3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.4.tgz#b8b5805e767b94d84af01f5527dbb4896326c478" - integrity sha512-oNwn+BAt3n9dK9uAYvI+XGlutwuTq/wfj4xCBaZCqwwVIGtD7D6ViihEbyYZrDHIHTDE3Q6oL3/hqmAyFEy9DQ== +"@vue/compiler-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" + integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q== dependencies: "@babel/parser" "^7.25.3" - "@vue/shared" "3.5.4" + "@vue/shared" "3.5.13" entities "^4.5.0" estree-walker "^2.0.2" source-map-js "^1.2.0" -"@vue/compiler-dom@3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.4.tgz#3f98e6ca76abab73630dad055b3ef6e2e6c2b006" - integrity sha512-yP9RRs4BDLOLfldn6ah+AGCNovGjMbL9uHvhDHf5wan4dAHLnFGOkqtfE7PPe4HTXIqE7l/NILdYw53bo1C8jw== +"@vue/compiler-dom@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" + integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== dependencies: - "@vue/compiler-core" "3.5.4" - "@vue/shared" "3.5.4" + "@vue/compiler-core" "3.5.13" + "@vue/shared" "3.5.13" "@vue/compiler-sfc@^3.3.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.4.tgz#a530accc9afed38506b14ce7ac6fb237eb09ff2d" - integrity sha512-P+yiPhL+NYH7m0ZgCq7AQR2q7OIE+mpAEgtkqEeH9oHSdIRvUO+4X6MPvblJIWcoe4YC5a2Gdf/RsoyP8FFiPQ== + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46" + integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ== dependencies: "@babel/parser" "^7.25.3" - "@vue/compiler-core" "3.5.4" - "@vue/compiler-dom" "3.5.4" - "@vue/compiler-ssr" "3.5.4" - "@vue/shared" "3.5.4" + "@vue/compiler-core" "3.5.13" + "@vue/compiler-dom" "3.5.13" + "@vue/compiler-ssr" "3.5.13" + "@vue/shared" "3.5.13" estree-walker "^2.0.2" magic-string "^0.30.11" - postcss "^8.4.44" + postcss "^8.4.48" source-map-js "^1.2.0" -"@vue/compiler-ssr@3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.4.tgz#b6d011adaca367e7cc364cb09dfb6a5c12ad974a" - integrity sha512-acESdTXsxPnYr2C4Blv0ggx5zIFMgOzZmYU2UgvIff9POdRGbRNBHRyzHAnizcItvpgerSKQbllUc9USp3V7eg== +"@vue/compiler-ssr@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba" + integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA== dependencies: - "@vue/compiler-dom" "3.5.4" - "@vue/shared" "3.5.4" + "@vue/compiler-dom" "3.5.13" + "@vue/shared" "3.5.13" -"@vue/shared@3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.4.tgz#d4768ddf13aded2774162298a3b5658cc999e1ee" - integrity sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA== +"@vue/shared@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" + integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== "@xmldom/xmldom@^0.8.3": version "0.8.10" @@ -1484,9 +1446,9 @@ acorn@^7.0.0, acorn@^7.1.1: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.0.0, acorn@^8.11.0, acorn@^8.4.1, acorn@^8.8.2, acorn@^8.9.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== adal-node@^0.2.2: version "0.2.4" @@ -1946,9 +1908,9 @@ axios@^0.26.0: follow-redirects "^1.14.8" axios@^1.3.4, axios@^1.7.5, axios@^1.7.7: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + version "1.7.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.8.tgz#1997b1496b394c21953e68c14aaa51b7b5de3d6e" + integrity sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -2017,11 +1979,11 @@ bluebird@^3.5.1: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + version "4.12.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.1.tgz#215741fe3c9dba2d7e12c001d0cfdbae43975ba7" + integrity sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg== -bn.js@^5.0.0, bn.js@^5.2.1: +bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -2287,7 +2249,7 @@ browserify-aes@^1.0.4, browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" -browserify-cipher@^1.0.0: +browserify-cipher@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== @@ -2307,14 +2269,15 @@ browserify-des@^1.0.0: safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + version "4.1.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.1.tgz#06e530907fe2949dc21fc3c2e2302e10b1437238" + integrity sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ== dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" + bn.js "^5.2.1" + randombytes "^2.1.0" + safe-buffer "^5.2.1" -browserify-sign@^4.0.0: +browserify-sign@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.3.tgz#7afe4c01ec7ee59a89a558a4b75bd85ae62d4208" integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw== @@ -2330,15 +2293,15 @@ browserify-sign@^4.0.0: readable-stream "^2.3.8" safe-buffer "^5.2.1" -browserslist@^4.23.1: - version "4.23.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" - integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== dependencies: - caniuse-lite "^1.0.30001646" - electron-to-chromium "^1.5.4" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" node-releases "^2.0.18" - update-browserslist-db "^1.1.0" + update-browserslist-db "^1.1.1" buffer-equal-constant-time@1.0.1: version "1.0.1" @@ -2451,10 +2414,10 @@ camelcase@^6.0.0, camelcase@^6.3.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001646: - version "1.0.30001660" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz#31218de3463fabb44d0b7607b652e56edf2e2355" - integrity sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg== +caniuse-lite@^1.0.30001669: + version "1.0.30001686" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz#0e04b8d90de8753188e93c9989d56cb19d902670" + integrity sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA== capture-stack-trace@^1.0.0: version "1.0.2" @@ -2588,12 +2551,12 @@ ci-info@^1.5.0: integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + version "1.0.6" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7" + integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + safe-buffer "^5.2.1" cjs-module-lexer@^1.2.2: version "1.4.1" @@ -2886,7 +2849,7 @@ count-lines@^0.1.2: resolved "https://registry.yarnpkg.com/count-lines/-/count-lines-0.1.2.tgz#e33493fb6860a82f7159d8237843fbfaefee5962" integrity sha512-YS8P4UYXX/hrDyLU3r/A5OcCNwdNbJFJckbe8j+x2Jhxsr2J4/rYl0sDwOljLZL7Uxc4s7mRSNcQD8dSjobz+g== -create-ecdh@^4.0.0: +create-ecdh@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== @@ -2912,7 +2875,7 @@ create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: +create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -2946,9 +2909,9 @@ cross-spawn@^5.0.1: which "^1.2.9" cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + version "6.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" + integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== dependencies: nice-try "^1.0.4" path-key "^2.0.1" @@ -2957,9 +2920,9 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: which "^1.2.9" cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -2971,21 +2934,22 @@ crypt@0.0.2: integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== crypto-browserify@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + version "3.12.1" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz#bb8921bec9acc81633379aa8f52d69b0b69e0dac" + integrity sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ== dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" + browserify-cipher "^1.0.1" + browserify-sign "^4.2.3" + create-ecdh "^4.0.4" + create-hash "^1.2.0" + create-hmac "^1.1.7" + diffie-hellman "^5.0.3" + hash-base "~3.0.4" + inherits "^2.0.4" + pbkdf2 "^3.1.2" + public-encrypt "^4.0.3" + randombytes "^2.1.0" + randomfill "^1.0.4" crypto-random-string@^1.0.0: version "1.0.0" @@ -3008,30 +2972,30 @@ css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -csv-generate@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-4.4.1.tgz#729781ace8d1b92f6bfb407d1ab9548728c55681" - integrity sha512-O/einO0v4zPmXaOV+sYqGa02VkST4GP5GLpWBNHEouIU7pF3kpGf3D0kCCvX82ydIY4EKkOK+R8b1BYsRXravg== +csv-generate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-4.4.2.tgz#001d7eda4993a72d24f73e15c9b9d7b251af8a2e" + integrity sha512-W6nVsf+rz0J3yo9FOjeer7tmzBJKaTTxf7K0uw6GZgRocZYPVpuSWWa5/aoWWrjQZj4/oNIKTYapOM7hiNjVMA== -csv-parse@^5.5.6: - version "5.5.6" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.6.tgz#0d726d58a60416361358eec291a9f93abe0b6b1a" - integrity sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A== +csv-parse@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.6.0.tgz#219beace2a3e9f28929999d2aa417d3fb3071c7f" + integrity sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q== -csv-stringify@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.5.1.tgz#a31837dd35e34787e3c248159c982a21af964d94" - integrity sha512-+9lpZfwpLntpTIEpFbwQyWuW/hmI/eHuJZD1XzeZpfZTqkf1fyvBbBLXTJJMsBuuS11uTShMqPwzx4A6ffXgRQ== +csv-stringify@^6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.5.2.tgz#b51d61cd949906d5b5b790463f3055d95915193e" + integrity sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA== csv@^6.2.2: - version "6.3.10" - resolved "https://registry.yarnpkg.com/csv/-/csv-6.3.10.tgz#960a3a9cef08573ecca2d80ddb71152aca383088" - integrity sha512-5NYZG4AN2ZUthmNxIudgBEdMPUnbQHu9V4QTzBPqQzUP3KQsFiJo+8HQ0+oVxj1PomIT1/f67VI1QH/hsrZLKA== + version "6.3.11" + resolved "https://registry.yarnpkg.com/csv/-/csv-6.3.11.tgz#c6dd1242e7543f67148827c6458ea668aee583f0" + integrity sha512-a8bhT76Q546jOElHcTrkzWY7Py925mfLO/jqquseH61ThOebYwOjLbWHBqdRB4K1VpU36sTyIei6Jwj7QdEZ7g== dependencies: - csv-generate "^4.4.1" - csv-parse "^5.5.6" - csv-stringify "^6.5.1" - stream-transform "^3.3.2" + csv-generate "^4.4.2" + csv-parse "^5.6.0" + csv-stringify "^6.5.2" + stream-transform "^3.3.3" d@1, d@^1.0.1, d@^1.0.2: version "1.0.2" @@ -3304,12 +3268,17 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.1.0, diff@^5.2.0: +diff@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== -diffie-hellman@^5.0.0: +diff@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" + integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== + +diffie-hellman@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== @@ -3377,9 +3346,9 @@ dot-prop@^4.2.1: is-obj "^1.0.0" dotenv@^16.4.1, dotenv@^16.4.5: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== dotenv@^8.2.0: version "8.6.0" @@ -3445,15 +3414,15 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.4: - version "1.5.21" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.21.tgz#f331794ee99045f2b2d73bc1d2dc654a13c462ee" - integrity sha512-+rBAerCpQvFSPyAO677i5gJuWGO2WFsoujENdcMzsrpP7Ebcc3pmpERgU8CV4fFF10a5haP4ivnFQ/AmLICBVg== +electron-to-chromium@^1.5.41: + version "1.5.68" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz#4f46be4d465ef00e2100d5557b66f4af70e3ce6c" + integrity sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ== elliptic@^6.5.3, elliptic@^6.5.5: - version "6.6.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210" - integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA== + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -3510,7 +3479,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: +entities@^4.2.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -3530,10 +3499,10 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.5: + version "1.23.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.5.tgz#f4599a4946d57ed467515ed10e4f157289cd52fb" + integrity sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -3550,7 +3519,7 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23 function.prototype.name "^1.1.6" get-intrinsic "^1.2.4" get-symbol-description "^1.0.2" - globalthis "^1.0.3" + globalthis "^1.0.4" gopd "^1.0.1" has-property-descriptors "^1.0.2" has-proto "^1.0.3" @@ -3566,10 +3535,10 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23 is-string "^1.0.7" is-typed-array "^1.1.13" is-weakref "^1.0.2" - object-inspect "^1.13.1" + object-inspect "^1.13.3" object-keys "^1.1.1" object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" + regexp.prototype.flags "^1.5.3" safe-array-concat "^1.1.2" safe-regex-test "^1.0.3" string.prototype.trim "^1.2.9" @@ -3618,13 +3587,13 @@ es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: hasown "^2.0.0" es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14: version "0.10.64" @@ -3692,7 +3661,7 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -3841,7 +3810,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -4222,11 +4191,11 @@ fast-redact@^3.1.1: integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== fast-uri@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" - integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== -fast-xml-parser@^4.3.2: +fast-xml-parser@^4.4.1: version "4.5.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37" integrity sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg== @@ -4370,9 +4339,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.15.6: version "1.15.9" @@ -4418,18 +4387,19 @@ form-data-encoder@1.7.2: integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + version "2.5.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2" + integrity sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q== dependencies: asynckit "^0.4.0" combined-stream "^1.0.6" mime-types "^2.1.12" + safe-buffer "^5.2.1" form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -4575,7 +4545,7 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -4727,7 +4697,7 @@ globals@^13.19.0, globals@^13.24.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: +globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== @@ -4747,12 +4717,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== +gopd@^1.0.1, gopd@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.1.0.tgz#df8f0839c2d48caefc32a025a49294d39606c912" + integrity sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA== dependencies: - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.4" got@^6.7.1: version "6.7.1" @@ -4776,10 +4746,10 @@ gpt-3-encoder@1.1.4: resolved "https://registry.yarnpkg.com/gpt-3-encoder/-/gpt-3-encoder-1.1.4.tgz#d6cdaacf5824857e133b6065247c757fc7e4fa72" integrity sha512-fSQRePV+HUAhCn7+7HL7lNIXNm6eaFWFbNLOOGtmSJ0qJycyQvj60OvRlH7mee8xAMjBDNRdMXlMwjAbMTDjkg== -gpt-tokenizer@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/gpt-tokenizer/-/gpt-tokenizer-2.5.1.tgz#ff1175b9ae1325f0f5281e9797af078cb29295dc" - integrity sha512-26zNjvGrIf+a6yWg5l2DvNT4LXAmotHyx7IomHVhXiUs62BwKVFLv/l8yRQQrkUDc2XDtzCdjcNuJqzOjxxiPA== +gpt-tokenizer@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/gpt-tokenizer/-/gpt-tokenizer-2.7.0.tgz#30cb445dd3102ca921c446db300f97a4a9d8a577" + integrity sha512-QjxaGgCZgKp8ecZzy7AmrCbYs+DD+y7GWSRwbe2ZiHPBs1EaK8xUIrt8irnmkAQcNMflpD27tk5yF4m9ig3wgw== graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.11" @@ -4821,7 +4791,7 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1, has-bigints@^1.0.2: +has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== @@ -4844,14 +4814,16 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: es-define-property "^1.0.0" has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.1.0.tgz#deb10494cbbe8809bce168a3b961f42969f5ed43" + integrity sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q== + dependencies: + call-bind "^1.0.7" -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" @@ -4900,13 +4872,13 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash-base@~3.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow== +hash-base@~3.0, hash-base@~3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.5.tgz#52480e285395cf7fba17dc4c9e47acdc7f248a8a" + integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + safe-buffer "^5.2.1" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" @@ -5091,9 +5063,9 @@ import-fresh@^3.2.1: resolve-from "^4.0.0" import-in-the-middle@^1.8.1: - version "1.11.0" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.0.tgz#a94c4925b8da18256cde3b3b7b38253e6ca5e708" - integrity sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q== + version "1.11.2" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz#dd848e72b63ca6cd7c34df8b8d97fc9baee6174f" + integrity sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA== dependencies: acorn "^8.8.2" acorn-import-attributes "^1.9.5" @@ -5184,12 +5156,19 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: - has-bigints "^1.0.1" + has-bigints "^1.0.2" is-binary-path@^1.0.0: version "1.0.1" @@ -5205,13 +5184,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== +is-boolean-object@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.0.tgz#9743641e80a62c094b5941c5bb791d66a88e497a" + integrity sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bind "^1.0.7" + has-tostringtag "^1.0.2" is-buffer@^1.1.5, is-buffer@^1.1.6, is-buffer@~1.1.6: version "1.1.6" @@ -5230,7 +5209,7 @@ is-builtin-module@^3.2.1: dependencies: builtin-modules "^3.3.0" -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: +is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -5263,7 +5242,7 @@ is-data-view@^1.0.1: dependencies: is-typed-array "^1.1.13" -is-date-object@^1.0.1: +is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== @@ -5308,6 +5287,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz#d74a7d0c5f3578e34a20729e69202e578d495dc2" + integrity sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA== + dependencies: + call-bind "^1.0.7" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -5318,6 +5304,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -5340,6 +5333,11 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" @@ -5350,12 +5348,13 @@ is-npm@^1.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" integrity sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg== -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== +is-number-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.0.tgz#5a867e9ecc3d294dda740d9f127835857af7eb05" + integrity sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw== dependencies: - has-tostringtag "^1.0.0" + call-bind "^1.0.7" + has-tostringtag "^1.0.2" is-number@^3.0.0: version "3.0.0" @@ -5409,18 +5408,25 @@ is-redirect@^1.0.0: integrity sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw== is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.0.tgz#41b9d266e7eb7451312c64efc37e8a7d453077cf" + integrity sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bind "^1.0.7" + gopd "^1.1.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" is-retry-allowed@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" @@ -5438,19 +5444,22 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== +is-string@^1.0.7, is-string@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.0.tgz#8cb83c5d57311bf8058bc6c8db294711641da45d" + integrity sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g== dependencies: - has-tostringtag "^1.0.0" + call-bind "^1.0.7" + has-tostringtag "^1.0.2" -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== +is-symbol@^1.0.4, is-symbol@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.0.tgz#ae993830a56d4781886d39f9f0a46b3e89b7b60b" + integrity sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A== dependencies: - has-symbols "^1.0.2" + call-bind "^1.0.7" + has-symbols "^1.0.3" + safe-regex-test "^1.0.3" is-typed-array@^1.1.13: version "1.1.13" @@ -5469,6 +5478,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -5476,6 +5490,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -5651,10 +5673,10 @@ jsdoc-type-pratt-parser@~4.0.0: resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== json-buffer@3.0.1: version "3.0.1" @@ -6050,9 +6072,9 @@ magic-string@^0.23.2: sourcemap-codec "^1.4.1" magic-string@^0.30.11: - version "0.30.11" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" - integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== + version "0.30.14" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.14.tgz#e9bb29870b81cfc1ec3cc656552f5a7fcbf19077" + integrity sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -6323,10 +6345,10 @@ mocha-junit-reporter@^2.0.0: strip-ansi "^6.0.1" xml "^1.0.1" -mocha@10.7.3, mocha@^10.7.3: - version "10.7.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" - integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== +mocha@10.8.2, mocha@^10.8.2: + version "10.8.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.8.2.tgz#8d8342d016ed411b12a429eb731b825f961afb96" + integrity sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg== dependencies: ansi-colors "^4.1.3" browser-stdout "^1.3.1" @@ -6440,9 +6462,9 @@ mutexify@^1.1.0: queue-tick "^1.0.0" nan@^2.12.1, nan@^2.14.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" - integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + version "2.22.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3" + integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== nanobench@^2.1.1: version "2.1.1" @@ -6455,9 +6477,9 @@ nanobench@^2.1.1: pretty-hrtime "^1.0.2" nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== nanomatch@^1.2.9: version "1.2.13" @@ -6481,11 +6503,16 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@0.6.3, negotiator@^0.6.2: +negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^0.6.2: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -6496,16 +6523,16 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nise@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.9.tgz#0cb73b5e4499d738231a473cd89bd8afbb618139" - integrity sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww== +nise@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^11.2.2" - "@sinonjs/text-encoding" "^0.7.2" + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" just-extend "^6.2.0" - path-to-regexp "^6.2.1" + path-to-regexp "^8.1.0" node-cleanup@^2.1.2: version "2.1.2" @@ -6674,10 +6701,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-inspect@^1.13.1, object-inspect@^1.13.3: + version "1.13.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== object-keys@^1.1.1: version "1.1.1" @@ -6891,9 +6918,9 @@ package-hash@^4.0.0: release-zalgo "^1.0.0" package-json-from-dist@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" - integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== package-json@^4.0.0: version "4.0.1" @@ -6948,11 +6975,11 @@ parse-passwd@^1.0.0: integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== dependencies: - domhandler "^5.0.2" + domhandler "^5.0.3" parse5 "^7.0.0" parse5-parser-stream@^7.1.2: @@ -6963,11 +6990,11 @@ parse5-parser-stream@^7.1.2: parse5 "^7.0.0" parse5@^7.0.0, parse5@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== dependencies: - entities "^4.4.0" + entities "^4.5.0" parseurl@~1.3.3: version "1.3.3" @@ -7032,10 +7059,10 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== -path-to-regexp@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" - integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== +path-to-regexp@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== path-type@^3.0.0: version "3.0.0" @@ -7061,7 +7088,7 @@ pause-stream@0.0.11: dependencies: through "~2.3" -pbkdf2@^3.0.3, pbkdf2@^3.1.2: +pbkdf2@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -7077,10 +7104,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picocolors@^1.0.0, picocolors@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -7158,14 +7185,14 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss@^8.4.44: - version "8.4.45" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" - integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== +postcss@^8.4.48: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== dependencies: nanoid "^3.3.7" - picocolors "^1.0.1" - source-map-js "^1.2.0" + picocolors "^1.1.1" + source-map-js "^1.2.1" prelude-ls@^1.2.1: version "1.2.1" @@ -7185,9 +7212,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" - integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + version "3.4.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b" + integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg== pretty-hrtime@^1.0.2: version "1.0.3" @@ -7200,9 +7227,9 @@ process-nextick-args@~2.0.0: integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process-on-spawn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" - integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== dependencies: fromentries "^1.2.0" @@ -7251,16 +7278,18 @@ pseudomap@^1.0.2: integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== psl@^1.1.28: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" pstree.remy@^1.1.7, pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== -public-encrypt@^4.0.0: +public-encrypt@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== @@ -7280,18 +7309,25 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@6.13.0, qs@^6.7.0: +qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: side-channel "^1.0.6" +qs@^6.7.0: + version "6.13.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -7324,7 +7360,7 @@ randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -randomfill@^1.0.3: +randomfill@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== @@ -7455,6 +7491,19 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +reflect.getprototypeof@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz#04311b33a1b713ca5eb7b5aed9950a86481858e5" + integrity sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + which-builtin-type "^1.1.4" + regenerator-runtime@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" @@ -7468,15 +7517,15 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== +regexp.prototype.flags@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" + integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== dependencies: - call-bind "^1.0.6" + call-bind "^1.0.7" define-properties "^1.2.1" es-errors "^1.3.0" - set-function-name "^2.0.1" + set-function-name "^2.0.2" registry-auth-token@^3.0.1: version "3.4.0" @@ -7896,7 +7945,7 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1: +set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -7954,9 +8003,9 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.6.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + version "1.8.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" + integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== shelljs@^0.8.5: version "0.8.5" @@ -8007,16 +8056,16 @@ simple-update-notifier@^2.0.0: dependencies: semver "^7.5.3" -sinon@^16.1.3: - version "16.1.3" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.3.tgz#b760ddafe785356e2847502657b4a0da5501fba8" - integrity sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA== +sinon@^19.0.2: + version "19.0.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-19.0.2.tgz#944cf771d22236aa84fc1ab70ce5bffc3a215dad" + integrity sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g== dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^10.3.0" - "@sinonjs/samsam" "^8.0.0" - diff "^5.1.0" - nise "^5.1.4" + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.2" + "@sinonjs/samsam" "^8.0.1" + diff "^7.0.0" + nise "^6.1.1" supports-color "^7.2.0" slash@^3.0.0: @@ -8061,7 +8110,7 @@ sonic-boom@^3.7.0: dependencies: atomic-sleep "^1.0.0" -source-map-js@^1.2.0: +source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -8297,10 +8346,10 @@ stream-shift@^1.0.2: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== -stream-transform@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-3.3.2.tgz#398c67b2f3b6ed5d04ceadde9e412bda8416c8ab" - integrity sha512-v64PUnPy9Qw94NGuaEMo+9RHQe4jTBYf+NkTtqkCgeuiNo8NlL0LtLR7fkKWNVFtp3RhIm5Dlxkgm5uz7TDimQ== +stream-transform@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-3.3.3.tgz#d130bfed33739de6ef190a8f06cc8ad1b4c1635a" + integrity sha512-dALXrXe+uq4aO5oStdHKlfCM/b3NBdouigvxVPxCdrMRAU6oHh3KNss20VbTPQNQmjAHzZGKGe66vgwegFEIog== string-argv@^0.3.1, string-argv@~0.3.1: version "0.3.2" @@ -8519,9 +8568,9 @@ supports-preserve-symlinks-flag@^1.0.0: integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== synckit@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" - integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== + version "0.9.2" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62" + integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw== dependencies: "@pkgr/core" "^0.1.0" tslib "^2.6.2" @@ -8552,9 +8601,9 @@ terser@^4.7.0: source-map-support "~0.5.12" terser@^5.15.1: - version "5.32.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.32.0.tgz#ee811c0d2d6b741c1cc34a2bc5bcbfc1b5b1f96c" - integrity sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ== + version "5.36.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e" + integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -8649,11 +8698,6 @@ tinyify@^4.0.0: through2 "^4.0.2" unassertify "^3.0.1" -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -8730,9 +8774,9 @@ trim-repeated@^1.0.0: escape-string-regexp "^1.0.2" ts-api-utils@^1.0.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" - integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-mocha@10.0.0: version "10.0.0" @@ -8776,10 +8820,10 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsc-watch@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tsc-watch/-/tsc-watch-6.2.0.tgz#4b191c36c6ed24c2bf6e721013af0825cd73d217" - integrity sha512-2LBhf9kjKXnz7KQ/puLHlozMzzUNHAdYBNMkg3eksQJ9GBAgMg8czznM83T5PmsoUvDnXzfIeQn2lNcIYDr8LA== +tsc-watch@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tsc-watch/-/tsc-watch-6.2.1.tgz#861801be929b2fd3d597c5f608db2b7ddba503db" + integrity sha512-GLwdz5Dy9K3sVm3RzgkLcyDpl5cvU9HEcE1A3gf5rqEwlUe7gDLxNCgcuNEw3zoKOiegMo3LnbF1t6HLqxhrSA== dependencies: cross-spawn "^7.0.3" node-cleanup "^2.1.2" @@ -8801,10 +8845,10 @@ tslib@^1.10.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.2.0, tslib@^2.6.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" - integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== +tslib@^2.2.0, tslib@^2.6.2, tslib@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== tunnel-agent@^0.6.0: version "0.6.0" @@ -8896,9 +8940,9 @@ typed-array-byte-length@^1.0.1: is-typed-array "^1.1.13" typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz#3fa9f22567700cc86aaf86a1e7176f74b59600f2" + integrity sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.7" @@ -8906,18 +8950,19 @@ typed-array-byte-offset@^1.0.2: gopd "^1.0.1" has-proto "^1.0.3" is-typed-array "^1.1.13" + reflect.getprototypeof "^1.0.6" typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== dependencies: call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-proto "^1.0.3" is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -8995,10 +9040,15 @@ undici-types@~6.19.2: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + undici@^6.19.5: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.8.tgz#002d7c8a28f8cc3a44ff33c3d4be4d85e15d40e1" - integrity sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g== + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.0.tgz#4b3d3afaef984e07b48e7620c34ed8a285ed4cd4" + integrity sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw== union-value@^1.0.0: version "1.0.1" @@ -9055,13 +9105,13 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-browserslist-db@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" - integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" + escalade "^3.2.0" + picocolors "^1.1.0" update-notifier@^2.5.0: version "2.5.0" @@ -9230,15 +9280,44 @@ whatwg-url@^5.0.0: webidl-conversions "^3.0.0" which-boxed-primitive@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz#2d850d6c4ac37b95441a67890e19f3fda8b6c6d9" + integrity sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.0" + is-number-object "^1.1.0" + is-string "^1.1.0" + is-symbol "^1.1.0" + +which-builtin-type@^1.1.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.0.tgz#58042ac9602d78a6d117c7e811349df1268ba63c" + integrity sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA== + dependencies: + call-bind "^1.0.7" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.2" + which-typed-array "^1.1.15" + +which-collection@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" which-module@^2.0.0: version "2.0.1" @@ -9246,9 +9325,9 @@ which-module@^2.0.0: integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + version "1.1.16" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" + integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.7" @@ -9445,9 +9524,9 @@ yaml@^1.10.0: integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3" - integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== yargs-parser@^13.1.2: version "13.1.2" diff --git a/python/packages/ai/teams/ai/moderators/azure_content_safety_moderator.py b/python/packages/ai/teams/ai/moderators/azure_content_safety_moderator.py index f838290e1..3a6bba845 100644 --- a/python/packages/ai/teams/ai/moderators/azure_content_safety_moderator.py +++ b/python/packages/ai/teams/ai/moderators/azure_content_safety_moderator.py @@ -49,6 +49,15 @@ class AzureContentSafetyModeratorOptions(OpenAIModeratorOptions): You could attach multiple lists name here. """ + halt_on_blocklist_hit: Optional[bool] = False + """ + When set to true, further analyses of harmful content will not be performed + in cases where blocklists are hit. + When set to false, all analyses of harmful content will be performed, whether + or not blocklists are hit. + Default value is false. + """ + class AzureContentSafetyModerator(Generic[StateT], Moderator[StateT]): """ @@ -95,6 +104,7 @@ async def review_input(self, context: TurnContext, state: StateT) -> Optional[Pl text=input, categories=self._options.categories, blocklist_names=self._options.blocklist_names, + halt_on_blocklist_hit=self._options.halt_on_blocklist_hit, ) ) @@ -102,21 +112,16 @@ async def review_input(self, context: TurnContext, state: StateT) -> Optional[Pl categories: Dict[str, bool] = {} category_scores: Dict[str, int] = {} - category_results = ["hateResult", "selfHarmResult", "sexualResult", "violenceResult"] - - for category in category_results: - result = res[category] if category in res else None - if result is not None: - category = result["category"].lower() - if category == "selfharm": - category = "self_harm" - categories[category] = result["severity"] is not None and result["severity"] > 0 - category_scores[category] = ( - 0 if result["severity"] is None else result["severity"] - ) - if result["severity"] is not None and result["severity"] > 0: - flagged = True + categories_analysis = res["categoriesAnalysis"] + for result in categories_analysis: + category = result["category"].lower() + if category == "selfharm": + category = "self_harm" + categories[category] = result["severity"] is not None and result["severity"] > 0 + category_scores[category] = 0 if result["severity"] is None else result["severity"] + if result["severity"] is not None and result["severity"] > 0: + flagged = True return ( None if not flagged @@ -156,6 +161,7 @@ async def review_output(self, context: TurnContext, state: StateT, plan: Plan) - ), categories=self._options.categories, blocklist_names=self._options.blocklist_names, + halt_on_blocklist_hit=self._options.halt_on_blocklist_hit, ) ) @@ -163,27 +169,20 @@ async def review_output(self, context: TurnContext, state: StateT, plan: Plan) - categories: Dict[str, bool] = {} category_scores: Dict[str, int] = {} - category_results = [ - "hateResult", - "selfHarmResult", - "sexualResult", - "violenceResult", - ] + categories_analysis = res["categoriesAnalysis"] - for category in category_results: - result = res[category] if category in res else None - if result is not None: - category = result["category"].lower() - if category == "selfharm": - category = "self_harm" - categories[category] = ( - result["severity"] is not None and result["severity"] > 0 - ) - category_scores[category] = ( - 0 if result["severity"] is None else result["severity"] - ) - if result["severity"] is not None and result["severity"] > 0: - flagged = True + for result in categories_analysis: + category = result["category"].lower() + if category == "selfharm": + category = "self_harm" + categories[category] = ( + result["severity"] is not None and result["severity"] > 0 + ) + category_scores[category] = ( + 0 if result["severity"] is None else result["severity"] + ) + if result["severity"] is not None and result["severity"] > 0: + flagged = True if flagged: return Plan( diff --git a/python/packages/ai/tests/ai/moderators/test_azure_content_safety_moderator.py b/python/packages/ai/tests/ai/moderators/test_azure_content_safety_moderator.py index 752b42201..5f1e6a498 100644 --- a/python/packages/ai/tests/ai/moderators/test_azure_content_safety_moderator.py +++ b/python/packages/ai/tests/ai/moderators/test_azure_content_safety_moderator.py @@ -28,11 +28,19 @@ def analyze_text(self, *_args, **_kwargs: Any): class MockContentSafetyClientWithResults: def analyze_text(self, *_args, **_kwargs: Any): return { - "blocklistsMatchResults": [], - "hateResult": {"category": "Hate", "severity": 6}, - "selfHarmResult": {"category": "SelfHarm", "severity": 0}, - "sexualResult": {"category": "Sexual", "severity": 0}, - "violenceResult": {"category": "Violence", "severity": 0}, + "blocklistsMatch": [ + { + "blocklistName": "string", + "blocklistItemId": "string", + "blocklistItemText": "bleed", + } + ], + "categoriesAnalysis": [ + {"category": "Hate", "severity": 6}, + {"category": "SelfHarm", "severity": 0}, + {"category": "Sexual", "severity": 0}, + {"category": "Violence", "severity": 0}, + ], } diff --git a/python/samples/05.chatModeration/src/bot.py b/python/samples/05.chatModeration/src/bot.py index ff5d1451a..bac4eaa8d 100644 --- a/python/samples/05.chatModeration/src/bot.py +++ b/python/samples/05.chatModeration/src/bot.py @@ -35,7 +35,7 @@ model = OpenAIModel( OpenAIModelOptions( api_key=config.OPENAI_KEY, - default_model="gpt-3.5-turbo" + default_model="gpt-4o" )) moderator = OpenAIModerator( OpenAIModeratorOptions( @@ -47,7 +47,7 @@ model = OpenAIModel( AzureOpenAIModelOptions( api_key=config.AZURE_OPENAI_KEY, - default_model="gpt-35-turbo", + default_model="gpt-4o", api_version="2023-03-15-preview", endpoint=config.AZURE_OPENAI_ENDPOINT )) @@ -55,7 +55,7 @@ AzureContentSafetyModeratorOptions( api_key=config.AZURE_CONTENT_SAFETY_KEY, moderate="both", - api_version="2023-04-30-preview", + api_version="2023-10-01", endpoint=config.AZURE_CONTENT_SAFETY_ENDPOINT ) ) diff --git a/python/samples/05.chatModeration/src/prompts/chat/config.json b/python/samples/05.chatModeration/src/prompts/chat/config.json index 0a10896bd..7deca377f 100644 --- a/python/samples/05.chatModeration/src/prompts/chat/config.json +++ b/python/samples/05.chatModeration/src/prompts/chat/config.json @@ -3,7 +3,7 @@ "description": "A bot that demonstrates content moderation.", "type": "completion", "completion": { - "model": "gpt-3.5-turbo", + "model": "gpt-4o", "completion_type": "chat", "include_history": true, "include_input": true, From bc4c3a517cff0b7ffbc3c6a95b5ad13ebfe25aed Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:33:50 -0500 Subject: [PATCH 13/18] [C#] fix: failure to load prompt due to json serialization error (#2227) ## Linked issues closes: #minor (issue number) ## Details - Bumping `JsonSchema` to v6.1.2 so that it uses `Json.More.Net` version 2.x - Fixed a test failure in a previous PR (.net build was not run) #### Change details _Exception_ ``` Microsoft.Teams.AI.Exceptions.TeamsAIException: Error while loading prompt. Method not found: \u0027!!0 Json.More.JsonSerializerOptionsExtensions.Read(System.Text.Json.JsonSerializerOptions, System.Text.Json.Utf8JsonReader ByRef)\u0027.\r\n at Microsoft.Teams.AI.AI.Prompts.PromptManager._LoadPromptTemplateFromFile(String name)\r\n at Microsoft.Teams.AI.AI.Prompts.PromptManager.GetPrompt(String name)\r\n at Program.\u003C\u003Ec__DisplayClass0_1.\u003C\u003C\u003CMain\u003E$\u003Eb__14\u003Ed.MoveNext() in C:\\TFS\\Icertis.AI.M365Copilot\\Icertis.AI.M365Copilot.App\\Program.cs:line 148\r\n--- End of stack trace from previous location ---\r\n at Microsoft.Teams.AI.AI.Planners.ActionPlanner\u00601.ContinueTaskAsync(ITurnContext context, TState state, AI\u00601 ai, CancellationToken cancellationToken)\r\n at Microsoft.Teams.AI.AI.Planners.ActionPlanner\u00601.BeginTaskAsync(ITurnContext context, TState state, AI\u00601 ai, CancellationToken cancellationToken)\r\n at Microsoft.Teams.AI.AI.AI\u00601.RunAsync(ITurnContext turnContext, TState turnState, Nullable\u00601 startTime, Int32 stepCount, CancellationToken cancellationToken)\r\n at Microsoft.Teams.AI.Application\u00601._OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)\r\n at Microsoft.Teams.AI.Application\u00601.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)\r\n at Microsoft.Teams.AI.Application.Authentication.Bot.FilteredTeamsSSOTokenExchangeMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken)\r\n at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken)\r\n at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) ``` ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs | 7 ++++++- .../Microsoft.TeamsAI/Microsoft.Teams.AI.csproj | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs index 4db309d8f..9278c1477 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/LLMClientTests.cs @@ -99,8 +99,10 @@ public async Task Test_CompletePromptAsync_PromptResponse_NotSuccess() LLMClientOptions options = new(promptCompletionModel, promptTemplate) { MaxHistoryMessages = 1 }; LLMClient client = new(options, null); TestMemory memory = new(); + ChatMessage message = new ChatMessage("Hi there"); promptCompletionModel.Results.Enqueue(new() { + Input = new List() { message }, Status = PromptResponseStatus.Error, Error = new TeamsAIException("test") }); @@ -113,7 +115,10 @@ public async Task Test_CompletePromptAsync_PromptResponse_NotSuccess() Assert.Equal(PromptResponseStatus.Error, response.Status); Assert.NotNull(response.Error); Assert.Equal("test", response.Error.Message); - Assert.Empty(memory.Values); + Assert.Single(memory.Values); + + IList conversation_history = (IList)memory.GetValue("conversation.history")!; + Assert.True(conversation_history[0].Content == message.Content); } [Fact] diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index 3025fc921..a6e4c34d4 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -38,7 +38,7 @@ - + From 852a5230e610904944c24ff3600e1c53121ac159 Mon Sep 17 00:00:00 2001 From: kavin <115390646+singhk97@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:13:03 -0500 Subject: [PATCH 14/18] [C#] bump: dotnet to v1.9.0 (#2229) ## Linked issues closes: #minor (issue number) ## Details - bump .NET sdk to v1.9.0 - bump AdaptiveCards.Templating to v2.x #### Change details > Describe your changes, with screenshots and code snippets as appropriate **code snippets**: **screenshots**: ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- .../Microsoft.TeamsAI/Microsoft.Teams.AI.csproj | 2 +- dotnet/samples/01.messaging.echoBot/EchoBot.csproj | 2 +- .../SearchCommand.csproj | 2 +- .../03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj | 2 +- dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj | 2 +- dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj | 2 +- .../samples/04.ai.c.actionMapping.lightBot/LightBot.csproj | 7 +++---- .../samples/04.ai.d.chainedActions.listBot/ListBot.csproj | 2 +- .../04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj | 2 +- dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj | 2 +- .../04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj | 2 +- dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj | 2 +- dotnet/samples/05.chatModeration/ChatModeration.csproj | 2 +- dotnet/samples/06.assistants.a.mathBot/MathBot.csproj | 2 +- dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj | 2 +- dotnet/samples/06.auth.oauth.bot/BotAuth.csproj | 2 +- .../MessageExtensionAuth.csproj | 2 +- dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj | 2 +- .../MessageExtensionAuth.csproj | 2 +- .../AzureAISearchBot/AzureAISearchBot.csproj | 2 +- .../08.datasource.azureopenai/AzureOpenAIBot.csproj | 2 +- 21 files changed, 23 insertions(+), 24 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj index a6e4c34d4..1955d60e7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Microsoft.Teams.AI.csproj @@ -7,7 +7,7 @@ enable Microsoft.Teams.AI Microsoft Teams AI SDK - 1.8.1 + 1.9.0 Microsoft Microsoft © Microsoft Corporation. All rights reserved. diff --git a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj index e41f28dc3..8ba0a6dc7 100644 --- a/dotnet/samples/01.messaging.echoBot/EchoBot.csproj +++ b/dotnet/samples/01.messaging.echoBot/EchoBot.csproj @@ -17,7 +17,7 @@ - + diff --git a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj index c6af635c8..65eebfb9d 100644 --- a/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj +++ b/dotnet/samples/02.messageExtensions.a.searchCommand/SearchCommand.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj index 5292b9795..3cab8fadc 100644 --- a/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj +++ b/dotnet/samples/03.adaptiveCards.a.typeAheadBot/TypeAheadBot.csproj @@ -13,7 +13,7 @@ - + diff --git a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj index 9ce3c4a91..5a23842c0 100644 --- a/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.a.teamsChefBot/TeamsChefBot.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj index d2efcaea5..5f07b0e89 100644 --- a/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj +++ b/dotnet/samples/04.ai.b.messageExtensions.gptME/GPT.csproj @@ -15,7 +15,7 @@ - + diff --git a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj index 1fcd96b55..0e58338c3 100644 --- a/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj +++ b/dotnet/samples/04.ai.c.actionMapping.lightBot/LightBot.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable @@ -16,13 +16,12 @@ - + - + - diff --git a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj index 4503d835c..18cc40f3c 100644 --- a/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj +++ b/dotnet/samples/04.ai.d.chainedActions.listBot/ListBot.csproj @@ -12,7 +12,7 @@ - + diff --git a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj index 9a92bcaa4..ef01b0fe1 100644 --- a/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj +++ b/dotnet/samples/04.ai.e.chainedActions.devOpsBot/DevOpsBot.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj index 05a7a3cb3..2c53b795b 100644 --- a/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj +++ b/dotnet/samples/04.ai.f.vision.cardMaster/CardGazer.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj index 00a762075..6287e4799 100644 --- a/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj +++ b/dotnet/samples/04.ai.g.teamsChefBot-streaming/TeamsChefBot.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj index 33faa98f3..5e402169a 100644 --- a/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj +++ b/dotnet/samples/04.e.twentyQuestions/TwentyQuestions.csproj @@ -13,7 +13,7 @@ - + diff --git a/dotnet/samples/05.chatModeration/ChatModeration.csproj b/dotnet/samples/05.chatModeration/ChatModeration.csproj index fe4bf3898..8cc1a1b59 100644 --- a/dotnet/samples/05.chatModeration/ChatModeration.csproj +++ b/dotnet/samples/05.chatModeration/ChatModeration.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj index 3bad03104..a6cbd3452 100644 --- a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj +++ b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj index b33d917d4..f0bb03bc9 100644 --- a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj +++ b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj @@ -17,7 +17,7 @@ - + diff --git a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj index 6b080750f..a5140f973 100644 --- a/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.oauth.bot/BotAuth.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj index e06ba9bf8..a7eb6ec24 100644 --- a/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.oauth.messageExtension/MessageExtensionAuth.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj index 86e26b01c..25f422de2 100644 --- a/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.bot/BotAuth.csproj @@ -18,7 +18,7 @@ - + diff --git a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj index 26cb4a83e..696aa3eba 100644 --- a/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj +++ b/dotnet/samples/06.auth.teamsSSO.messageExtension/MessageExtensionAuth.csproj @@ -16,7 +16,7 @@ - + diff --git a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj index 1ea7d35da..fef679bcf 100644 --- a/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj +++ b/dotnet/samples/08.datasource.azureaisearch/AzureAISearchBot/AzureAISearchBot.csproj @@ -15,7 +15,7 @@ - + diff --git a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj index fbc8b66b1..1157e6d6c 100644 --- a/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj +++ b/dotnet/samples/08.datasource.azureopenai/AzureOpenAIBot.csproj @@ -15,7 +15,7 @@ - + From df94bb1af152e134392146730e94fee9e950f92f Mon Sep 17 00:00:00 2001 From: Kavin Date: Wed, 11 Dec 2024 11:48:41 -0800 Subject: [PATCH 15/18] remove weird addition --- .../04.ai-apps/e.assistants-orderBot/devTools/teamsapptester | 1 - js/yarn.lock | 5 ----- 2 files changed, 6 deletions(-) delete mode 120000 js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester diff --git a/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester b/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester deleted file mode 120000 index e6800eee4..000000000 --- a/js/samples/04.ai-apps/e.assistants-orderBot/devTools/teamsapptester +++ /dev/null @@ -1 +0,0 @@ -/Users/corina/.fx/bin/testTool/0.2.4 \ No newline at end of file diff --git a/js/yarn.lock b/js/yarn.lock index 6e187beb2..52227566b 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -824,11 +824,6 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== -"@opentelemetry/semantic-conventions@^1.19.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" - integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== - "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" From 68aa6705e314548b79ce37e53d15a45850aeef78 Mon Sep 17 00:00:00 2001 From: Kavin Date: Wed, 11 Dec 2024 12:21:47 -0800 Subject: [PATCH 16/18] revert bracket style init --- .../AI/Models/StreamingChatToolCallsBuilder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs index 80f10c10f..a43b78527 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs @@ -5,9 +5,9 @@ namespace Microsoft.Teams.AI.AI.Models { public class StreamingChatToolCallsBuilder { - private readonly Dictionary _indexToToolCallId = []; - private readonly Dictionary _indexToFunctionName = []; - private readonly Dictionary> _indexToFunctionArguments = []; + private readonly Dictionary _indexToToolCallId = new(); + private readonly Dictionary _indexToFunctionName = new(); + private readonly Dictionary> _indexToFunctionArguments = new(); public void Append(StreamingChatToolCallUpdate toolCallUpdate) { @@ -39,7 +39,7 @@ public void Append(StreamingChatToolCallUpdate toolCallUpdate) public IReadOnlyList Build() { - List toolCalls = []; + List toolCalls = new(); foreach (KeyValuePair indexToToolCallIdPair in _indexToToolCallId) { From ba4f6b7892e4f98bef7fa02a6626f46e1584341f Mon Sep 17 00:00:00 2001 From: Kavin Date: Wed, 11 Dec 2024 12:22:44 -0800 Subject: [PATCH 17/18] editor style change --- .../packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig index 5e51edaf1..24b8ee970 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/.editorconfig @@ -2,3 +2,6 @@ # CS0618: Type or member is obsolete dotnet_diagnostic.CS0618.severity = silent + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = silent From 5091d510b50c0c74973eb5e519627b8ad0528df1 Mon Sep 17 00:00:00 2001 From: Kavin Date: Wed, 11 Dec 2024 13:25:06 -0800 Subject: [PATCH 18/18] fix warnings --- .../Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs | 6 +++--- .../AI/Models/StreamingChatToolCallsBuilder.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs index 17105b53a..979105d67 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/SequenceBuilder.cs @@ -3,10 +3,10 @@ namespace Microsoft.Teams.AI.AI.Models { - public class SequenceBuilder + internal class SequenceBuilder { - private Segment _first; - private Segment _last; + private Segment? _first; + private Segment? _last; public void Append(ReadOnlyMemory data) { diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs index a43b78527..78b1f23b7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Models/StreamingChatToolCallsBuilder.cs @@ -3,7 +3,7 @@ namespace Microsoft.Teams.AI.AI.Models { - public class StreamingChatToolCallsBuilder + internal class StreamingChatToolCallsBuilder { private readonly Dictionary _indexToToolCallId = new(); private readonly Dictionary _indexToFunctionName = new();