diff --git a/OpenAI.sln b/OpenAI.sln index d6350d85f..3d2ce81e7 100644 --- a/OpenAI.sln +++ b/OpenAI.sln @@ -6,7 +6,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI", "src\OpenAI.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI.Examples", "examples\OpenAI.Examples.csproj", "{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{6F156401-2544-41D7-B204-3148C51C1D09}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{6F156401-2544-41D7-B204-3148C51C1D09}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\azure-sdk-for-net\sdk\core\System.ClientModel\src\System.ClientModel.csproj", "{8269B350-8875-49F9-A9EA-DEF8718FEBE4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,6 +28,10 @@ Global {6F156401-2544-41D7-B204-3148C51C1D09}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F156401-2544-41D7-B204-3148C51C1D09}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F156401-2544-41D7-B204-3148C51C1D09}.Release|Any CPU.Build.0 = Release|Any CPU + {8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -33,4 +39,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A97F4B90-2591-4689-B1F8-5F21FE6D6CAE} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal diff --git a/README.md b/README.md index 017165071..88e4b572c 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,11 @@ When you request a chat completion, the default behavior is for the server to ge The client library offers a convenient approach to working with streaming chat completions. If you wanted to re-write the example from the previous section using streaming, rather than calling the `ChatClient`'s `CompleteChat` method, you would call its `CompleteChatStreaming` method instead: ```csharp -ResultCollection updates +CollectionResult updates = client.CompleteChatStreaming("Say 'this is a test.'"); ``` -Notice that the returned value is a `ResultCollection` instance, which can be enumerated to process the streaming response chunks as they arrive: +Notice that the returned value is a `CollectionResult` instance, which can be enumerated to process the streaming response chunks as they arrive: ```csharp Console.WriteLine($"[ASSISTANT]:"); @@ -132,10 +132,10 @@ foreach (StreamingChatCompletionUpdate update in updates) } ``` -Alternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncResultCollection` and enumerate it using `await foreach`: +Alternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncCollectionResult` and enumerate it using `await foreach`: ```csharp -AsyncResultCollection updates +AsyncCollectionResult updates = client.CompleteChatStreamingAsync("Say 'this is a test.'"); Console.WriteLine($"[ASSISTANT]:"); @@ -528,7 +528,7 @@ Finally, you can use the `AssistantClient`'s `GetMessages` method to retrieve th For illustrative purposes, you could print the messages to the console and also save any images produced by the assistant to local storage: ```csharp -PageableCollection messages = assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst); +PageCollection messages = assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst); foreach (ThreadMessage message in messages) { @@ -640,10 +640,10 @@ AssistantThread thread = assistantClient.CreateThread(new ThreadCreationOptions( }); ``` -With the assistant and thread prepared, use the `CreateRunStreaming` method to get an enumerable `ResultCollection`. You can then iterate over this collection with `foreach`. For async calling patterns, use `CreateRunStreamingAsync` and iterate over the `AsyncResultCollection` with `await foreach`, instead. Note that streaming variants also exist for `CreateThreadAndRunStreaming` and `SubmitToolOutputsToRunStreaming`. +With the assistant and thread prepared, use the `CreateRunStreaming` method to get an enumerable `CollectionResult`. You can then iterate over this collection with `foreach`. For async calling patterns, use `CreateRunStreamingAsync` and iterate over the `AsyncCollectionResult` with `await foreach`, instead. Note that streaming variants also exist for `CreateThreadAndRunStreaming` and `SubmitToolOutputsToRunStreaming`. ```csharp -ResultCollection streamingUpdates = assistantClient.CreateRunStreaming( +CollectionResult streamingUpdates = assistantClient.CreateRunStreaming( thread, assistant, new RunCreationOptions() diff --git a/examples/Assistants/Example01_RetrievalAugmentedGeneration.cs b/examples/Assistants/Example01_RetrievalAugmentedGeneration.cs index fb9189ac9..d95fce7d3 100644 --- a/examples/Assistants/Example01_RetrievalAugmentedGeneration.cs +++ b/examples/Assistants/Example01_RetrievalAugmentedGeneration.cs @@ -88,18 +88,13 @@ public void Example01_RetrievalAugmentedGeneration() InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." } }; - ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions); - - // Check back to see when the run is done - do - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id); - } while (!threadRun.Status.IsTerminal); + // Passing ReturnWhen.Completed means CreateThreadAndRun will return control after the run is complete. + ThreadRunOperation runOperation = assistantClient.CreateThreadAndRun(ReturnWhen.Completed, assistant.Id, threadOptions); // Finally, we'll print out the full history for the thread that includes the augmented generation - PageableCollection messages - = assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst); + PageCollection messagePages + = assistantClient.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }); + IEnumerable messages = messagePages.GetAllValues(); foreach (ThreadMessage message in messages) { @@ -142,7 +137,7 @@ PageableCollection messages } // Optionally, delete any persistent resources you no longer need. - _ = assistantClient.DeleteThread(threadRun.ThreadId); + _ = assistantClient.DeleteThread(runOperation.ThreadId); _ = assistantClient.DeleteAssistant(assistant); _ = fileClient.DeleteFile(salesFile); } diff --git a/examples/Assistants/Example01_RetrievalAugmentedGenerationAsync.cs b/examples/Assistants/Example01_RetrievalAugmentedGenerationAsync.cs index 6cde83f33..4b33f95c0 100644 --- a/examples/Assistants/Example01_RetrievalAugmentedGenerationAsync.cs +++ b/examples/Assistants/Example01_RetrievalAugmentedGenerationAsync.cs @@ -89,18 +89,12 @@ public async Task Example01_RetrievalAugmentedGenerationAsync() InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." } }; - ThreadRun threadRun = await assistantClient.CreateThreadAndRunAsync(assistant.Id, threadOptions); - - // Check back to see when the run is done - do - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id); - } while (!threadRun.Status.IsTerminal); + ThreadRunOperation runOperation = await assistantClient.CreateThreadAndRunAsync(ReturnWhen.Completed, assistant.Id, threadOptions); // Finally, we'll print out the full history for the thread that includes the augmented generation - AsyncPageableCollection messages - = assistantClient.GetMessagesAsync(threadRun.ThreadId, ListOrder.OldestFirst); + AsyncPageCollection messagePages + = assistantClient.GetMessagesAsync(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }); + IAsyncEnumerable messages = messagePages.GetAllValuesAsync(); await foreach (ThreadMessage message in messages) { @@ -143,7 +137,7 @@ AsyncPageableCollection messages } // Optionally, delete any persistent resources you no longer need. - _ = await assistantClient.DeleteThreadAsync(threadRun.ThreadId); + _ = await assistantClient.DeleteThreadAsync(runOperation.ThreadId); _ = await assistantClient.DeleteAssistantAsync(assistant); _ = await fileClient.DeleteFileAsync(salesFile); } diff --git a/examples/Assistants/Example02_FunctionCalling.cs b/examples/Assistants/Example02_FunctionCalling.cs index a60491379..c4700c85f 100644 --- a/examples/Assistants/Example02_FunctionCalling.cs +++ b/examples/Assistants/Example02_FunctionCalling.cs @@ -85,22 +85,23 @@ string GetCurrentWeather(string location, string unit = "celsius") InitialMessages = { "What's the weather like today?" } }; - ThreadRun run = client.CreateThreadAndRun(assistant.Id, threadOptions); + ThreadRunOperation runOperation = client.CreateThreadAndRun(ReturnWhen.Started, assistant.Id, threadOptions); #endregion #region // Poll the run until it is no longer queued or in progress. - while (!run.Status.IsTerminal) + while (!runOperation.HasCompleted) { Thread.Sleep(TimeSpan.FromSeconds(1)); - run = client.GetRun(run.ThreadId, run.Id); // If the run requires action, resolve them. - if (run.Status == RunStatus.RequiresAction) + if (runOperation.Status == RunStatus.RequiresAction) { List toolOutputs = []; - foreach (RequiredAction action in run.RequiredActions) + // TODO: Maybe improve API around this? + + foreach (RequiredAction action in runOperation.Value.RequiredActions) { switch (action.FunctionName) { @@ -142,17 +143,18 @@ string GetCurrentWeather(string location, string unit = "celsius") } // Submit the tool outputs to the assistant, which returns the run to the queued state. - run = client.SubmitToolOutputsToRun(run.ThreadId, run.Id, toolOutputs); + runOperation.SubmitToolOutputsToRun(toolOutputs); } } #endregion #region // With the run complete, list the messages and display their content - if (run.Status == RunStatus.Completed) + if (runOperation.Status == RunStatus.Completed) { - PageableCollection messages - = client.GetMessages(run.ThreadId, resultOrder: ListOrder.OldestFirst); + PageCollection messagePages + = client.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }); + IEnumerable messages = messagePages.GetAllValues(); foreach (ThreadMessage message in messages) { @@ -185,7 +187,7 @@ PageableCollection messages } else { - throw new NotImplementedException(run.Status.ToString()); + throw new NotImplementedException(runOperation.Status.ToString()); } #endregion } diff --git a/examples/Assistants/Example02_FunctionCallingAsync.cs b/examples/Assistants/Example02_FunctionCallingAsync.cs index 08bbd0bde..1fc1d7d12 100644 --- a/examples/Assistants/Example02_FunctionCallingAsync.cs +++ b/examples/Assistants/Example02_FunctionCallingAsync.cs @@ -85,22 +85,21 @@ string GetCurrentWeather(string location, string unit = "celsius") InitialMessages = { "What's the weather like today?" } }; - ThreadRun run = await client.CreateThreadAndRunAsync(assistant.Id, threadOptions); + ThreadRunOperation runOperation = await client.CreateThreadAndRunAsync(ReturnWhen.Started, assistant.Id, threadOptions); #endregion #region // Poll the run until it is no longer queued or in progress. - while (!run.Status.IsTerminal) + while (!runOperation.HasCompleted) { await Task.Delay(TimeSpan.FromSeconds(1)); - run = await client.GetRunAsync(run.ThreadId, run.Id); - + // If the run requires action, resolve them. - if (run.Status == RunStatus.RequiresAction) + if (runOperation.Status == RunStatus.RequiresAction) { List toolOutputs = []; - foreach (RequiredAction action in run.RequiredActions) + foreach (RequiredAction action in runOperation.Value.RequiredActions) { switch (action.FunctionName) { @@ -142,17 +141,18 @@ string GetCurrentWeather(string location, string unit = "celsius") } // Submit the tool outputs to the assistant, which returns the run to the queued state. - run = await client.SubmitToolOutputsToRunAsync(run.ThreadId, run.Id, toolOutputs); + await runOperation.SubmitToolOutputsToRunAsync(toolOutputs); } } #endregion #region // With the run complete, list the messages and display their content - if (run.Status == RunStatus.Completed) + if (runOperation.Status == RunStatus.Completed) { - AsyncPageableCollection messages - = client.GetMessagesAsync(run.ThreadId, resultOrder: ListOrder.OldestFirst); + AsyncPageCollection messagePages + = client.GetMessagesAsync(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }); + IAsyncEnumerable messages = messagePages.GetAllValuesAsync(); await foreach (ThreadMessage message in messages) { @@ -185,7 +185,7 @@ AsyncPageableCollection messages } else { - throw new NotImplementedException(run.Status.ToString()); + throw new NotImplementedException(runOperation.Status.ToString()); } #endregion } diff --git a/examples/Assistants/Example02b_FunctionCallingStreaming.cs b/examples/Assistants/Example02b_FunctionCallingStreaming.cs index cb3bf7343..86e7149f6 100644 --- a/examples/Assistants/Example02b_FunctionCallingStreaming.cs +++ b/examples/Assistants/Example02b_FunctionCallingStreaming.cs @@ -1,139 +1,138 @@ -using NUnit.Framework; -using OpenAI.Assistants; -using System; -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Threading.Tasks; +//using NUnit.Framework; +//using OpenAI.Assistants; +//using System; +//using System.ClientModel; +//using System.ClientModel.Primitives; +//using System.Collections.Generic; +//using System.Threading.Tasks; -namespace OpenAI.Examples; +//namespace OpenAI.Examples; -public partial class AssistantExamples -{ - [Test] - public async Task Example02b_FunctionCallingStreaming() - { - // This example parallels the content at the following location: - // https://platform.openai.com/docs/assistants/tools/function-calling/function-calling-beta - #region Step 1 - Define Functions +//public partial class AssistantExamples +//{ +// [Test] +// public async Task Example02b_FunctionCallingStreaming() +// { +// // This example parallels the content at the following location: +// // https://platform.openai.com/docs/assistants/tools/function-calling/function-calling-beta +// #region Step 1 - Define Functions - // First, define the functions that the assistant will use in its defined tools. +// // First, define the functions that the assistant will use in its defined tools. - FunctionToolDefinition getTemperatureTool = new() - { - FunctionName = "get_current_temperature", - Description = "Gets the current temperature at a specific location.", - Parameters = BinaryData.FromString(""" - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g., San Francisco, CA" - }, - "unit": { - "type": "string", - "enum": ["Celsius", "Fahrenheit"], - "description": "The temperature unit to use. Infer this from the user's location." - } - } - } - """), - }; +// FunctionToolDefinition getTemperatureTool = new() +// { +// FunctionName = "get_current_temperature", +// Description = "Gets the current temperature at a specific location.", +// Parameters = BinaryData.FromString(""" +// { +// "type": "object", +// "properties": { +// "location": { +// "type": "string", +// "description": "The city and state, e.g., San Francisco, CA" +// }, +// "unit": { +// "type": "string", +// "enum": ["Celsius", "Fahrenheit"], +// "description": "The temperature unit to use. Infer this from the user's location." +// } +// } +// } +// """), +// }; - FunctionToolDefinition getRainProbabilityTool = new() - { - FunctionName = "get_current_rain_probability", - Description = "Gets the current forecasted probability of rain at a specific location," - + " represented as a percent chance in the range of 0 to 100.", - Parameters = BinaryData.FromString(""" - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g., San Francisco, CA" - } - }, - "required": ["location"] - } - """), - }; +// FunctionToolDefinition getRainProbabilityTool = new() +// { +// FunctionName = "get_current_rain_probability", +// Description = "Gets the current forecasted probability of rain at a specific location," +// + " represented as a percent chance in the range of 0 to 100.", +// Parameters = BinaryData.FromString(""" +// { +// "type": "object", +// "properties": { +// "location": { +// "type": "string", +// "description": "The city and state, e.g., San Francisco, CA" +// } +// }, +// "required": ["location"] +// } +// """), +// }; - #endregion +// #endregion - // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. -#pragma warning disable OPENAI001 - AssistantClient client = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); +// // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +//#pragma warning disable OPENAI001 +// AssistantClient client = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - #region Create a new assistant with function tools - // Create an assistant that can call the function tools. - AssistantCreationOptions assistantOptions = new() - { - Name = "Example: Function Calling", - Instructions = - "Don't make assumptions about what values to plug into functions." - + " Ask for clarification if a user request is ambiguous.", - Tools = { getTemperatureTool, getRainProbabilityTool }, - }; +// #region Create a new assistant with function tools +// // Create an assistant that can call the function tools. +// AssistantCreationOptions assistantOptions = new() +// { +// Name = "Example: Function Calling", +// Instructions = +// "Don't make assumptions about what values to plug into functions." +// + " Ask for clarification if a user request is ambiguous.", +// Tools = { getTemperatureTool, getRainProbabilityTool }, +// }; - Assistant assistant = await client.CreateAssistantAsync("gpt-4-turbo", assistantOptions); - #endregion +// Assistant assistant = await client.CreateAssistantAsync("gpt-4-turbo", assistantOptions); +// #endregion - #region Step 2 - Create a thread and add messages - AssistantThread thread = await client.CreateThreadAsync(); - ThreadMessage message = await client.CreateMessageAsync( - thread, - MessageRole.User, - [ - "What's the weather in San Francisco today and the likelihood it'll rain?" - ]); - #endregion +// #region Step 2 - Create a thread and add messages +// AssistantThread thread = await client.CreateThreadAsync(); +// ThreadMessage message = await client.CreateMessageAsync( +// thread, +// MessageRole.User, +// [ +// "What's the weather in San Francisco today and the likelihood it'll rain?" +// ]); +// #endregion - #region Step 3 - Initiate a streaming run - // TODO: replace this with finalized enumerable result pattern - AsyncResultCollection asyncUpdates - = client.CreateRunStreamingAsync(thread, assistant); +// #region Step 3 - Initiate a streaming run +// AsyncCollectionResult asyncUpdates +// = client.CreateRunStreamingAsync(thread, assistant); - ThreadRun currentRun = null; - do - { - currentRun = null; - List outputsToSubmit = []; - await foreach (StreamingUpdate update in asyncUpdates) - { - if (update is RunUpdate runUpdate) - { - currentRun = runUpdate; - } - else if (update is RequiredActionUpdate requiredActionUpdate) - { - if (requiredActionUpdate.FunctionName == getTemperatureTool.FunctionName) - { - outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "57")); - } - else if (requiredActionUpdate.FunctionName == getRainProbabilityTool.FunctionName) - { - outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "25%")); - } - } - else if (update is MessageContentUpdate contentUpdate) - { - Console.Write(contentUpdate.Text); - } - } - if (outputsToSubmit.Count > 0) - { - asyncUpdates = client.SubmitToolOutputsToRunStreamingAsync(currentRun, outputsToSubmit); - } - } - while (currentRun?.Status.IsTerminal == false); +// ThreadRun currentRun = null; +// do +// { +// currentRun = null; +// List outputsToSubmit = []; +// await foreach (StreamingUpdate update in asyncUpdates) +// { +// if (update is RunUpdate runUpdate) +// { +// currentRun = runUpdate; +// } +// else if (update is RequiredActionUpdate requiredActionUpdate) +// { +// if (requiredActionUpdate.FunctionName == getTemperatureTool.FunctionName) +// { +// outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "57")); +// } +// else if (requiredActionUpdate.FunctionName == getRainProbabilityTool.FunctionName) +// { +// outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "25%")); +// } +// } +// else if (update is MessageContentUpdate contentUpdate) +// { +// Console.Write(contentUpdate.Text); +// } +// } +// if (outputsToSubmit.Count > 0) +// { +// asyncUpdates = client.SubmitToolOutputsToRunStreamingAsync(currentRun, outputsToSubmit); +// } +// } +// while (currentRun?.Status.IsTerminal == false); - #endregion +// #endregion - // Optionally, delete the resources for tidiness if no longer needed. - RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow }; - _ = await client.DeleteThreadAsync(thread.Id, noThrowOptions); - _ = await client.DeleteAssistantAsync(assistant.Id, noThrowOptions); - } -} +// // Optionally, delete the resources for tidiness if no longer needed. +// RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow }; +// _ = await client.DeleteThreadAsync(thread.Id, noThrowOptions); +// _ = await client.DeleteAssistantAsync(assistant.Id, noThrowOptions); +// } +//} diff --git a/examples/Assistants/Example03_ListAssistantsWithPagination.cs b/examples/Assistants/Example03_ListAssistantsWithPagination.cs index 1c49470c4..e8640f1c5 100644 --- a/examples/Assistants/Example03_ListAssistantsWithPagination.cs +++ b/examples/Assistants/Example03_ListAssistantsWithPagination.cs @@ -2,6 +2,7 @@ using OpenAI.Assistants; using System; using System.ClientModel; +using System.Collections.Generic; namespace OpenAI.Examples; @@ -16,7 +17,8 @@ public void Example03_ListAssistantsWithPagination() int count = 0; - PageableCollection assistants = client.GetAssistants(); + PageCollection assitantPages = client.GetAssistants(); + IEnumerable assistants = assitantPages.GetAllValues(); foreach (Assistant assistant in assistants) { Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); diff --git a/examples/Assistants/Example03_ListAssistantsWithPaginationAsync.cs b/examples/Assistants/Example03_ListAssistantsWithPaginationAsync.cs index 145dcfeab..fa67a713f 100644 --- a/examples/Assistants/Example03_ListAssistantsWithPaginationAsync.cs +++ b/examples/Assistants/Example03_ListAssistantsWithPaginationAsync.cs @@ -2,6 +2,7 @@ using OpenAI.Assistants; using System; using System.ClientModel; +using System.Collections.Generic; using System.Threading.Tasks; namespace OpenAI.Examples; @@ -17,7 +18,8 @@ public async Task Example03_ListAssistantsWithPaginationAsync() int count = 0; - AsyncPageableCollection assistants = client.GetAssistantsAsync(); + AsyncPageCollection assitantPages = client.GetAssistantsAsync(); + IAsyncEnumerable assistants = assitantPages.GetAllValuesAsync(); await foreach (Assistant assistant in assistants) { Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); diff --git a/examples/Assistants/Example04_AllTheTools.cs b/examples/Assistants/Example04_AllTheTools.cs index 30c8fc9fd..40ceb7932 100644 --- a/examples/Assistants/Example04_AllTheTools.cs +++ b/examples/Assistants/Example04_AllTheTools.cs @@ -89,22 +89,21 @@ static string GetNameOfFamilyMember(string relation) } }); - ThreadRun run = client.CreateRun(thread, assistant); + ThreadRunOperation runOperation = client.CreateRun(ReturnWhen.Started, thread, assistant); #endregion #region Complete the run, calling functions as needed // Poll the run until it is no longer queued or in progress. - while (!run.Status.IsTerminal) + while (!runOperation.HasCompleted) { Thread.Sleep(TimeSpan.FromSeconds(1)); - run = client.GetRun(run.ThreadId, run.Id); - + // If the run requires action, resolve them. - if (run.Status == RunStatus.RequiresAction) + if (runOperation.Status == RunStatus.RequiresAction) { List toolOutputs = []; - foreach (RequiredAction action in run.RequiredActions) + foreach (RequiredAction action in runOperation.Value.RequiredActions) { switch (action.FunctionName) { @@ -128,17 +127,18 @@ static string GetNameOfFamilyMember(string relation) } // Submit the tool outputs to the assistant, which returns the run to the queued state. - run = client.SubmitToolOutputsToRun(run.ThreadId, run.Id, toolOutputs); + runOperation.SubmitToolOutputsToRun(toolOutputs); } } #endregion #region // With the run complete, list the messages and display their content - if (run.Status == RunStatus.Completed) + if (runOperation.Status == RunStatus.Completed) { - PageableCollection messages - = client.GetMessages(run.ThreadId, resultOrder: ListOrder.OldestFirst); + PageCollection messagePages + = client.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }); + IEnumerable messages = messagePages.GetAllValues(); foreach (ThreadMessage message in messages) { @@ -171,8 +171,8 @@ PageableCollection messages #endregion #region List run steps for details about tool calls - PageableCollection runSteps = client.GetRunSteps(run, resultOrder: ListOrder.OldestFirst); - foreach (RunStep step in runSteps) + PageCollection runSteps = runOperation.GetRunSteps(new RunStepCollectionOptions() { Order = ListOrder.OldestFirst }); + foreach (RunStep step in runSteps.GetAllValues()) { Console.WriteLine($"Run step: {step.Status}"); foreach (RunStepToolCall toolCall in step.Details.ToolCalls) @@ -188,7 +188,7 @@ PageableCollection messages } else { - throw new NotImplementedException(run.Status.ToString()); + throw new NotImplementedException(runOperation.Status.ToString()); } #endregion diff --git a/examples/Assistants/Example05_AssistantsWithVision.cs b/examples/Assistants/Example05_AssistantsWithVision.cs index e73222261..f996ec93c 100644 --- a/examples/Assistants/Example05_AssistantsWithVision.cs +++ b/examples/Assistants/Example05_AssistantsWithVision.cs @@ -1,72 +1,72 @@ -using NUnit.Framework; -using OpenAI.Assistants; -using OpenAI.Files; -using System; -using System.ClientModel; +//using NUnit.Framework; +//using OpenAI.Assistants; +//using OpenAI.Files; +//using System; +//using System.ClientModel; -namespace OpenAI.Examples; +//namespace OpenAI.Examples; -public partial class AssistantExamples -{ - [Test] - public void Example05_AssistantsWithVision() - { - // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. -#pragma warning disable OPENAI001 - OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - FileClient fileClient = openAIClient.GetFileClient(); - AssistantClient assistantClient = openAIClient.GetAssistantClient(); +//public partial class AssistantExamples +//{ +// [Test] +// public void Example05_AssistantsWithVision() +// { +// // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +//#pragma warning disable OPENAI001 +// OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); +// FileClient fileClient = openAIClient.GetFileClient(); +// AssistantClient assistantClient = openAIClient.GetAssistantClient(); - OpenAIFileInfo pictureOfAppleFile = fileClient.UploadFile( - "picture-of-apple.jpg", - FileUploadPurpose.Vision); - Uri linkToPictureOfOrange = new("https://platform.openai.com/fictitious-files/picture-of-orange.png"); +// OpenAIFileInfo pictureOfAppleFile = fileClient.UploadFile( +// "picture-of-apple.jpg", +// FileUploadPurpose.Vision); +// Uri linkToPictureOfOrange = new("https://platform.openai.com/fictitious-files/picture-of-orange.png"); - Assistant assistant = assistantClient.CreateAssistant( - "gpt-4o", - new AssistantCreationOptions() - { - Instructions = "When asked a question, attempt to answer very concisely. " - + "Prefer one-sentence answers whenever feasible." - }); +// Assistant assistant = assistantClient.CreateAssistant( +// "gpt-4o", +// new AssistantCreationOptions() +// { +// Instructions = "When asked a question, attempt to answer very concisely. " +// + "Prefer one-sentence answers whenever feasible." +// }); - AssistantThread thread = assistantClient.CreateThread(new ThreadCreationOptions() - { - InitialMessages = - { - new ThreadInitializationMessage( - MessageRole.User, - [ - "Hello, assistant! Please compare these two images for me:", - MessageContent.FromImageFileId(pictureOfAppleFile.Id), - MessageContent.FromImageUrl(linkToPictureOfOrange), - ]), - } - }); +// AssistantThread thread = assistantClient.CreateThread(new ThreadCreationOptions() +// { +// InitialMessages = +// { +// new ThreadInitializationMessage( +// MessageRole.User, +// [ +// "Hello, assistant! Please compare these two images for me:", +// MessageContent.FromImageFileId(pictureOfAppleFile.Id), +// MessageContent.FromImageUrl(linkToPictureOfOrange), +// ]), +// } +// }); - ResultCollection streamingUpdates = assistantClient.CreateRunStreaming( - thread, - assistant, - new RunCreationOptions() - { - AdditionalInstructions = "When possible, try to sneak in puns if you're asked to compare things.", - }); +// CollectionResult streamingUpdates = assistantClient.CreateRunStreaming( +// thread, +// assistant, +// new RunCreationOptions() +// { +// AdditionalInstructions = "When possible, try to sneak in puns if you're asked to compare things.", +// }); - foreach (StreamingUpdate streamingUpdate in streamingUpdates) - { - if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated) - { - Console.WriteLine($"--- Run started! ---"); - } - if (streamingUpdate is MessageContentUpdate contentUpdate) - { - Console.Write(contentUpdate.Text); - } - } +// foreach (StreamingUpdate streamingUpdate in streamingUpdates) +// { +// if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated) +// { +// Console.WriteLine($"--- Run started! ---"); +// } +// if (streamingUpdate is MessageContentUpdate contentUpdate) +// { +// Console.Write(contentUpdate.Text); +// } +// } - // Delete temporary resources, if desired - _ = fileClient.DeleteFile(pictureOfAppleFile); - _ = assistantClient.DeleteThread(thread); - _ = assistantClient.DeleteAssistant(assistant); - } -} +// // Delete temporary resources, if desired +// _ = fileClient.DeleteFile(pictureOfAppleFile); +// _ = assistantClient.DeleteThread(thread); +// _ = assistantClient.DeleteAssistant(assistant); +// } +//} diff --git a/examples/Assistants/Example05_AssistantsWithVisionAsync.cs b/examples/Assistants/Example05_AssistantsWithVisionAsync.cs index 68e50ed50..34241a380 100644 --- a/examples/Assistants/Example05_AssistantsWithVisionAsync.cs +++ b/examples/Assistants/Example05_AssistantsWithVisionAsync.cs @@ -1,72 +1,72 @@ -using NUnit.Framework; -using OpenAI.Assistants; -using OpenAI.Files; -using System; -using System.ClientModel; -using System.Threading.Tasks; +//using NUnit.Framework; +//using OpenAI.Assistants; +//using OpenAI.Files; +//using System; +//using System.ClientModel; +//using System.Threading.Tasks; -namespace OpenAI.Examples; +//namespace OpenAI.Examples; -public partial class AssistantExamples -{ - [Test] - public async Task Example05_AssistantsWithVisionAsync() - { - // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. -#pragma warning disable OPENAI001 - OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - FileClient fileClient = openAIClient.GetFileClient(); - AssistantClient assistantClient = openAIClient.GetAssistantClient(); +//public partial class AssistantExamples +//{ +// [Test] +// public async Task Example05_AssistantsWithVisionAsync() +// { +// // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +//#pragma warning disable OPENAI001 +// OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY")); +// FileClient fileClient = openAIClient.GetFileClient(); +// AssistantClient assistantClient = openAIClient.GetAssistantClient(); - OpenAIFileInfo pictureOfAppleFile = await fileClient.UploadFileAsync( - "picture-of-apple.jpg", - FileUploadPurpose.Vision); - Uri linkToPictureOfOrange = new("https://platform.openai.com/fictitious-files/picture-of-orange.png"); +// OpenAIFileInfo pictureOfAppleFile = await fileClient.UploadFileAsync( +// "picture-of-apple.jpg", +// FileUploadPurpose.Vision); +// Uri linkToPictureOfOrange = new("https://platform.openai.com/fictitious-files/picture-of-orange.png"); - Assistant assistant = await assistantClient.CreateAssistantAsync( - "gpt-4o", - new AssistantCreationOptions() - { - Instructions = "When asked a question, attempt to answer very concisely. " - + "Prefer one-sentence answers whenever feasible." - }); +// Assistant assistant = await assistantClient.CreateAssistantAsync( +// "gpt-4o", +// new AssistantCreationOptions() +// { +// Instructions = "When asked a question, attempt to answer very concisely. " +// + "Prefer one-sentence answers whenever feasible." +// }); - AssistantThread thread = await assistantClient.CreateThreadAsync(new ThreadCreationOptions() - { - InitialMessages = - { - new ThreadInitializationMessage( - MessageRole.User, - [ - "Hello, assistant! Please compare these two images for me:", - MessageContent.FromImageFileId(pictureOfAppleFile.Id), - MessageContent.FromImageUrl(linkToPictureOfOrange), - ]), - } - }); +// AssistantThread thread = await assistantClient.CreateThreadAsync(new ThreadCreationOptions() +// { +// InitialMessages = +// { +// new ThreadInitializationMessage( +// MessageRole.User, +// [ +// "Hello, assistant! Please compare these two images for me:", +// MessageContent.FromImageFileId(pictureOfAppleFile.Id), +// MessageContent.FromImageUrl(linkToPictureOfOrange), +// ]), +// } +// }); - AsyncResultCollection streamingUpdates = assistantClient.CreateRunStreamingAsync( - thread, - assistant, - new RunCreationOptions() - { - AdditionalInstructions = "When possible, try to sneak in puns if you're asked to compare things.", - }); +// AsyncCollectionResult streamingUpdates = assistantClient.CreateRunStreamingAsync( +// thread, +// assistant, +// new RunCreationOptions() +// { +// AdditionalInstructions = "When possible, try to sneak in puns if you're asked to compare things.", +// }); - await foreach (StreamingUpdate streamingUpdate in streamingUpdates) - { - if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated) - { - Console.WriteLine($"--- Run started! ---"); - } - if (streamingUpdate is MessageContentUpdate contentUpdate) - { - Console.Write(contentUpdate.Text); - } - } +// await foreach (StreamingUpdate streamingUpdate in streamingUpdates) +// { +// if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated) +// { +// Console.WriteLine($"--- Run started! ---"); +// } +// if (streamingUpdate is MessageContentUpdate contentUpdate) +// { +// Console.Write(contentUpdate.Text); +// } +// } - _ = await fileClient.DeleteFileAsync(pictureOfAppleFile); - _ = await assistantClient.DeleteThreadAsync(thread); - _ = await assistantClient.DeleteAssistantAsync(assistant); - } -} +// _ = await fileClient.DeleteFileAsync(pictureOfAppleFile); +// _ = await assistantClient.DeleteThreadAsync(thread); +// _ = await assistantClient.DeleteAssistantAsync(assistant); +// } +//} diff --git a/examples/Chat/Example02_SimpleChatStreaming.cs b/examples/Chat/Example02_SimpleChatStreaming.cs index cad64d9b0..50b8938f0 100644 --- a/examples/Chat/Example02_SimpleChatStreaming.cs +++ b/examples/Chat/Example02_SimpleChatStreaming.cs @@ -12,7 +12,7 @@ public void Example02_SimpleChatStreaming() { ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - ResultCollection updates + CollectionResult updates = client.CompleteChatStreaming("Say 'this is a test.'"); Console.WriteLine($"[ASSISTANT]:"); diff --git a/examples/Chat/Example02_SimpleChatStreamingAsync.cs b/examples/Chat/Example02_SimpleChatStreamingAsync.cs index 123b5e887..c22bb4d8f 100644 --- a/examples/Chat/Example02_SimpleChatStreamingAsync.cs +++ b/examples/Chat/Example02_SimpleChatStreamingAsync.cs @@ -13,7 +13,7 @@ public async Task Example02_SimpleChatStreamingAsync() { ChatClient client = new(model: "gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - AsyncResultCollection updates + AsyncCollectionResult updates = client.CompleteChatStreamingAsync("Say 'this is a test.'"); Console.WriteLine($"[ASSISTANT]:"); diff --git a/examples/Chat/Example04_FunctionCallingStreaming.cs b/examples/Chat/Example04_FunctionCallingStreaming.cs index e0e799d89..3f0770692 100644 --- a/examples/Chat/Example04_FunctionCallingStreaming.cs +++ b/examples/Chat/Example04_FunctionCallingStreaming.cs @@ -38,7 +38,7 @@ public void Example04_FunctionCallingStreaming() Dictionary indexToFunctionName = []; Dictionary indexToFunctionArguments = []; StringBuilder contentBuilder = new(); - ResultCollection chatUpdates + CollectionResult chatUpdates = client.CompleteChatStreaming(messages, options); foreach (StreamingChatCompletionUpdate chatUpdate in chatUpdates) diff --git a/examples/Chat/Example04_FunctionCallingStreamingAsync.cs b/examples/Chat/Example04_FunctionCallingStreamingAsync.cs index dc3eae564..6fac2c494 100644 --- a/examples/Chat/Example04_FunctionCallingStreamingAsync.cs +++ b/examples/Chat/Example04_FunctionCallingStreamingAsync.cs @@ -39,7 +39,7 @@ public async Task Example04_FunctionCallingStreamingAsync() Dictionary indexToFunctionName = []; Dictionary indexToFunctionArguments = []; StringBuilder contentBuilder = new(); - AsyncResultCollection chatUpdates + AsyncCollectionResult chatUpdates = client.CompleteChatStreamingAsync(messages, options); await foreach (StreamingChatCompletionUpdate chatUpdate in chatUpdates) diff --git a/src/Custom/Assistants/AssistantClient.Convenience.cs b/src/Custom/Assistants/AssistantClient.Convenience.cs index 510bbee5f..9057d9aeb 100644 --- a/src/Custom/Assistants/AssistantClient.Convenience.cs +++ b/src/Custom/Assistants/AssistantClient.Convenience.cs @@ -28,7 +28,6 @@ public virtual Task> ModifyAssistantAsync(Assistant assi public virtual ClientResult ModifyAssistant(Assistant assistant, AssistantModificationOptions options) => ModifyAssistant(assistant?.Id, options); - /// /// Deletes an existing . /// @@ -129,36 +128,30 @@ public virtual ClientResult CreateMessage( /// Returns a collection of instances from an existing . /// /// The thread to list messages from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of messages that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetMessagesAsync( + /// Options describing the collection to return. + /// + public virtual AsyncPageCollection GetMessagesAsync( AssistantThread thread, - ListOrder? resultOrder = default) + MessageCollectionOptions options = default) { Argument.AssertNotNull(thread, nameof(thread)); - return GetMessagesAsync(thread.Id, resultOrder); + return GetMessagesAsync(thread.Id, options); } /// /// Returns a collection of instances from an existing . /// /// The thread to list messages from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of messages that can be enumerated using foreach. - public virtual PageableCollection GetMessages( + /// Options describing the collection to return. + /// + public virtual PageCollection GetMessages( AssistantThread thread, - ListOrder? resultOrder = default) + MessageCollectionOptions options = default) { Argument.AssertNotNull(thread, nameof(thread)); - return GetMessages(thread.Id, resultOrder); + return GetMessages(thread.Id, options); } /// @@ -219,45 +212,53 @@ public virtual ClientResult DeleteMessage(ThreadMessage message) /// The assistant that should be used when evaluating the thread. /// Additional options for the run. /// A new instance. - public virtual Task> CreateRunAsync(AssistantThread thread, Assistant assistant, RunCreationOptions options = null) - => CreateRunAsync(thread?.Id, assistant?.Id, options); - - /// - /// Begins a new that evaluates a using a specified - /// . - /// - /// The thread that the run should evaluate. - /// The assistant that should be used when evaluating the thread. - /// Additional options for the run. - /// A new instance. - public virtual ClientResult CreateRun(AssistantThread thread, Assistant assistant, RunCreationOptions options = null) - => CreateRun(thread?.Id, assistant?.Id, options); - - /// - /// Begins a new streaming that evaluates a using a specified - /// . - /// - /// The thread that the run should evaluate. - /// The assistant that should be used when evaluating the thread. - /// Additional options for the run. - public virtual AsyncResultCollection CreateRunStreamingAsync( + public virtual async Task CreateRunAsync( + ReturnWhen returnWhen, AssistantThread thread, Assistant assistant, RunCreationOptions options = null) - => CreateRunStreamingAsync(thread?.Id, assistant?.Id, options); + => await CreateRunAsync(returnWhen, thread?.Id, assistant?.Id, options).ConfigureAwait(false); /// - /// Begins a new streaming that evaluates a using a specified + /// Begins a new that evaluates a using a specified /// . /// /// The thread that the run should evaluate. /// The assistant that should be used when evaluating the thread. /// Additional options for the run. - public virtual ResultCollection CreateRunStreaming( - AssistantThread thread, - Assistant assistant, + /// A new instance. + public virtual ThreadRunOperation CreateRun( + ReturnWhen returnWhen, + AssistantThread thread, + Assistant assistant, RunCreationOptions options = null) - => CreateRunStreaming(thread?.Id, assistant?.Id, options); + => CreateRun(returnWhen, thread?.Id, assistant?.Id, options); + + ///// + ///// Begins a new streaming that evaluates a using a specified + ///// . + ///// + ///// The thread that the run should evaluate. + ///// The assistant that should be used when evaluating the thread. + ///// Additional options for the run. + //public virtual AsyncCollectionResult CreateRunStreamingAsync( + // AssistantThread thread, + // Assistant assistant, + // RunCreationOptions options = null) + // => CreateRunStreamingAsync(thread?.Id, assistant?.Id, options); + + ///// + ///// Begins a new streaming that evaluates a using a specified + ///// . + ///// + ///// The thread that the run should evaluate. + ///// The assistant that should be used when evaluating the thread. + ///// Additional options for the run. + //public virtual CollectionResult CreateRunStreaming( + // AssistantThread thread, + // Assistant assistant, + // RunCreationOptions options = null) + // => CreateRunStreaming(thread?.Id, assistant?.Id, options); /// /// Creates a new thread and immediately begins a run against it using the specified . @@ -266,11 +267,12 @@ public virtual ResultCollection CreateRunStreaming( /// Options for the new thread that will be created. /// Additional options to apply to the run that will begin. /// A new . - public virtual Task> CreateThreadAndRunAsync( + public virtual async Task CreateThreadAndRunAsync( + ReturnWhen returnWhen, Assistant assistant, ThreadCreationOptions threadOptions = null, RunCreationOptions runOptions = null) - => CreateThreadAndRunAsync(assistant?.Id, threadOptions, runOptions); + => await CreateThreadAndRunAsync(returnWhen, assistant?.Id, threadOptions, runOptions).ConfigureAwait(false); /// /// Creates a new thread and immediately begins a run against it using the specified . @@ -279,187 +281,64 @@ public virtual Task> CreateThreadAndRunAsync( /// Options for the new thread that will be created. /// Additional options to apply to the run that will begin. /// A new . - public virtual ClientResult CreateThreadAndRun( - Assistant assistant, - ThreadCreationOptions threadOptions = null, - RunCreationOptions runOptions = null) - => CreateThreadAndRun(assistant?.Id, threadOptions, runOptions); - - /// - /// Creates a new thread and immediately begins a streaming run against it using the specified . - /// - /// The assistant that the new run should use. - /// Options for the new thread that will be created. - /// Additional options to apply to the run that will begin. - public virtual AsyncResultCollection CreateThreadAndRunStreamingAsync( - Assistant assistant, - ThreadCreationOptions threadOptions = null, - RunCreationOptions runOptions = null) - => CreateThreadAndRunStreamingAsync(assistant?.Id, threadOptions, runOptions); - - /// - /// Creates a new thread and immediately begins a streaming run against it using the specified . - /// - /// The assistant that the new run should use. - /// Options for the new thread that will be created. - /// Additional options to apply to the run that will begin. - public virtual ResultCollection CreateThreadAndRunStreaming( + public virtual ThreadRunOperation CreateThreadAndRun( + ReturnWhen returnWhen, Assistant assistant, ThreadCreationOptions threadOptions = null, RunCreationOptions runOptions = null) - => CreateThreadAndRunStreaming(assistant?.Id, threadOptions, runOptions); + => CreateThreadAndRun(returnWhen, assistant?.Id, threadOptions, runOptions); + + ///// + ///// Creates a new thread and immediately begins a streaming run against it using the specified . + ///// + ///// The assistant that the new run should use. + ///// Options for the new thread that will be created. + ///// Additional options to apply to the run that will begin. + //public virtual AsyncCollectionResult CreateThreadAndRunStreamingAsync( + // Assistant assistant, + // ThreadCreationOptions threadOptions = null, + // RunCreationOptions runOptions = null) + // => CreateThreadAndRunStreamingAsync(assistant?.Id, threadOptions, runOptions); + + ///// + ///// Creates a new thread and immediately begins a streaming run against it using the specified . + ///// + ///// The assistant that the new run should use. + ///// Options for the new thread that will be created. + ///// Additional options to apply to the run that will begin. + //public virtual CollectionResult CreateThreadAndRunStreaming( + // Assistant assistant, + // ThreadCreationOptions threadOptions = null, + // RunCreationOptions runOptions = null) + // => CreateThreadAndRunStreaming(assistant?.Id, threadOptions, runOptions); /// /// Returns a collection of instances associated with an existing . /// /// The thread that runs in the list should be associated with. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of runs that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetRunsAsync( + /// Options describing the collection to return. + /// + public virtual AsyncPageCollection GetRunsAsync( AssistantThread thread, - ListOrder? resultOrder = default) + RunCollectionOptions options = default) { Argument.AssertNotNull(thread, nameof(thread)); - return GetRunsAsync(thread.Id, resultOrder); + return GetRunsAsync(thread.Id, options); } /// /// Returns a collection of instances associated with an existing . /// /// The thread that runs in the list should be associated with. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of runs that can be enumerated using foreach. - public virtual PageableCollection GetRuns( + /// Options describing the collection to return. + /// + public virtual PageCollection GetRuns( AssistantThread thread, - ListOrder? resultOrder = default) + RunCollectionOptions options = default) { Argument.AssertNotNull(thread, nameof(thread)); - return GetRuns(thread.Id, resultOrder); - } - - /// - /// Gets a refreshed instance of an existing . - /// - /// The run to get a refreshed instance of. - /// A new instance with updated information. - public virtual Task> GetRunAsync(ThreadRun run) - => GetRunAsync(run?.ThreadId, run?.Id); - - /// - /// Gets a refreshed instance of an existing . - /// - /// The run to get a refreshed instance of. - /// A new instance with updated information. - public virtual ClientResult GetRun(ThreadRun run) - => GetRun(run?.ThreadId, run?.Id); - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run. - /// - /// The run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - /// The , updated after the submission was processed. - public virtual Task> SubmitToolOutputsToRunAsync( - ThreadRun run, - IEnumerable toolOutputs) - => SubmitToolOutputsToRunAsync(run?.ThreadId, run?.Id, toolOutputs); - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run. - /// - /// The run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - /// The , updated after the submission was processed. - public virtual ClientResult SubmitToolOutputsToRun( - ThreadRun run, - IEnumerable toolOutputs) - => SubmitToolOutputsToRun(run?.ThreadId, run?.Id, toolOutputs); - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. - /// - /// The run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - public virtual AsyncResultCollection SubmitToolOutputsToRunStreamingAsync( - ThreadRun run, - IEnumerable toolOutputs) - => SubmitToolOutputsToRunStreamingAsync(run?.ThreadId, run?.Id, toolOutputs); - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. - /// - /// The run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - public virtual ResultCollection SubmitToolOutputsToRunStreaming( - ThreadRun run, - IEnumerable toolOutputs) - => SubmitToolOutputsToRunStreaming(run?.ThreadId, run?.Id, toolOutputs); - - /// - /// Cancels an in-progress . - /// - /// The run to cancel. - /// An updated instance, reflecting the new status of the run. - public virtual Task> CancelRunAsync(ThreadRun run) - => CancelRunAsync(run?.ThreadId, run?.Id); - - /// - /// Cancels an in-progress . - /// - /// The run to cancel. - /// An updated instance, reflecting the new status of the run. - public virtual ClientResult CancelRun(ThreadRun run) - => CancelRun(run?.ThreadId, run?.Id); - - /// - /// Gets a collection of instances associated with a . - /// - /// The run to list run steps from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of run steps that can be enumerated using await foreach. - public virtual PageableCollection GetRunSteps( - ThreadRun run, - ListOrder? resultOrder = default) - { - Argument.AssertNotNull(run, nameof(run)); - - return GetRunSteps(run.ThreadId, run.Id, resultOrder); - } - - /// - /// Gets a collection of instances associated with a . - /// - /// The run to list run steps from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A collection of run steps that can be enumerated using foreach. - public virtual AsyncPageableCollection GetRunStepsAsync( - ThreadRun run, - ListOrder? resultOrder = default) - { - Argument.AssertNotNull(run, nameof(run)); - - return GetRunStepsAsync(run.ThreadId, run.Id, resultOrder); + return GetRuns(thread.Id, options); } } diff --git a/src/Custom/Assistants/AssistantClient.Protocol.cs b/src/Custom/Assistants/AssistantClient.Protocol.cs index e6912702e..e7c0dfb68 100644 --- a/src/Custom/Assistants/AssistantClient.Protocol.cs +++ b/src/Custom/Assistants/AssistantClient.Protocol.cs @@ -1,6 +1,8 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; namespace OpenAI.Assistants; @@ -63,10 +65,10 @@ public virtual ClientResult CreateAssistant(BinaryContent content, RequestOption /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// Service returned a non-success status code. /// The response returned from the service. - public virtual async Task GetAssistantsAsync(int? limit, string order, string after, string before, RequestOptions options) + public virtual IAsyncEnumerable GetAssistantsAsync(int? limit, string order, string after, string before, RequestOptions options) { - using PipelineMessage message = CreateGetAssistantsRequest(limit, order, after, before, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + AssistantsPageEnumerator enumerator = new AssistantsPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -93,10 +95,10 @@ public virtual async Task GetAssistantsAsync(int? limit, string or /// The request options, which can override default behaviors of the client pipeline on a per-call basis. /// Service returned a non-success status code. /// The response returned from the service. - public virtual ClientResult GetAssistants(int? limit, string order, string after, string before, RequestOptions options) + public virtual IEnumerable GetAssistants(int? limit, string order, string after, string before, RequestOptions options) { - using PipelineMessage message = CreateGetAssistantsRequest(limit, order, after, before, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + AssistantsPageEnumerator enumerator = new AssistantsPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); } /// @@ -213,13 +215,21 @@ public virtual Task CreateMessageAsync(string threadId, BinaryCont public virtual ClientResult CreateMessage(string threadId, BinaryContent content, RequestOptions options = null) => _messageSubClient.CreateMessage(threadId, content, options); - /// - public virtual Task GetMessagesAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) - => _messageSubClient.GetMessagesAsync(threadId, limit, order, after, before, options); + public virtual IAsyncEnumerable GetMessagesAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + + PageResultEnumerator enumerator = new MessagesPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual IEnumerable GetMessages(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - /// - public virtual ClientResult GetMessages(string threadId, int? limit, string order, string after, string before, RequestOptions options) - => _messageSubClient.GetMessages(threadId, limit, order, after, before, options); + PageResultEnumerator enumerator = new MessagesPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); + } /// public virtual Task GetMessageAsync(string threadId, string messageId, RequestOptions options) @@ -243,77 +253,149 @@ public virtual Task DeleteMessageAsync(string threadId, string mes public virtual ClientResult DeleteMessage(string threadId, string messageId, RequestOptions options) => _messageSubClient.DeleteMessage(threadId, messageId, options); - /// - public virtual Task CreateThreadAndRunAsync(BinaryContent content, RequestOptions options = null) - => _runSubClient.CreateThreadAndRunAsync(content, options); - - /// - public virtual ClientResult CreateThreadAndRun(BinaryContent content, RequestOptions options = null) - => _runSubClient.CreateThreadAndRun(content, options = null); - - /// - public virtual Task CreateRunAsync(string threadId, BinaryContent content, RequestOptions options = null) - => _runSubClient.CreateRunAsync(threadId, content, options); - - /// - public virtual ClientResult CreateRun(string threadId, BinaryContent content, RequestOptions options = null) - => _runSubClient.CreateRun(threadId, content, options); - - /// - public virtual Task GetRunsAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) - => _runSubClient.GetRunsAsync(threadId, limit, order, after, before, options); + public virtual async Task CreateThreadAndRunAsync( + ReturnWhen returnWhen, + BinaryContent content, + RequestOptions options = null) + { + ClientResult result = await _runSubClient.CreateThreadAndRunAsync(content, options).ConfigureAwait(false); + + // Protocol level: get values needed to create subclient from response + PipelineResponse response = result.GetRawResponse(); + using JsonDocument doc = JsonDocument.Parse(response.Content); + string threadId = doc.RootElement.GetProperty("thread_id"u8).GetString()!; + string runId = doc.RootElement.GetProperty("id"u8).GetString()!; + + // Create the poller + ThreadRunPoller poller = new ThreadRunPoller(_pipeline, _endpoint, result, threadId, runId, options); + + // Create the operation subclient + ThreadRunOperation operation = new ThreadRunOperation( + _pipeline, _endpoint, + threadId, runId, result.GetRawResponse(), + poller); + + if (returnWhen == ReturnWhen.Started) + { + return operation; + } + + operation.WaitForCompletionResult(); + return operation; + } - /// - public virtual ClientResult GetRuns(string threadId, int? limit, string order, string after, string before, RequestOptions options) - => _runSubClient.GetRuns(threadId, limit, order, after, before, options); + public virtual ThreadRunOperation CreateThreadAndRun( + ReturnWhen returnWhen, + BinaryContent content, + RequestOptions options = null) + { + ClientResult result = _runSubClient.CreateThreadAndRun(content, options); + + // Protocol level: get values needed to create subclient from response + PipelineResponse response = result.GetRawResponse(); + using JsonDocument doc = JsonDocument.Parse(response.Content); + string threadId = doc.RootElement.GetProperty("thread_id"u8).GetString()!; + string runId = doc.RootElement.GetProperty("id"u8).GetString()!; + + // Create the poller + ThreadRunPoller poller = new ThreadRunPoller(_pipeline, _endpoint, result, threadId, runId, options); + + // Create the operation subclient + ThreadRunOperation operation = new ThreadRunOperation( + _pipeline, _endpoint, + threadId, runId, result.GetRawResponse(), + poller); + + if (returnWhen == ReturnWhen.Started) + { + return operation; + } + + operation.WaitForCompletionResult(); + return operation; + } - /// - public virtual Task GetRunAsync(string threadId, string runId, RequestOptions options) - => _runSubClient.GetRunAsync(threadId, runId, options); + public virtual IAsyncEnumerable GetRunsAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - /// - public virtual ClientResult GetRun(string threadId, string runId, RequestOptions options) - => _runSubClient.GetRun(threadId, runId, options); + PageResultEnumerator enumerator = new RunsPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + return PageCollectionHelpers.CreateAsync(enumerator); + } - /// - public virtual Task ModifyRunAsync(string threadId, string runId, BinaryContent content, RequestOptions options = null) - => _runSubClient.ModifyRunAsync(threadId, runId, content, options); + public virtual IEnumerable GetRuns(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - /// - public virtual ClientResult ModifyRun(string threadId, string runId, BinaryContent content, RequestOptions options = null) - => _runSubClient.ModifyRun(threadId, runId, content, options); + PageResultEnumerator enumerator = new RunsPageEnumerator(_pipeline, _endpoint, threadId, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); + } - /// - public virtual Task CancelRunAsync(string threadId, string runId, RequestOptions options) - => _runSubClient.CancelRunAsync(threadId, runId, options); + public virtual async Task CreateRunAsync( + ReturnWhen returnWhen, + string threadId, + BinaryContent content, + RequestOptions options = null) + { + ClientResult result = await _runSubClient.CreateRunAsync(threadId, content, options).ConfigureAwait(false); + + // Protocol level: get values needed to create subclient from response + PipelineResponse response = result.GetRawResponse(); + using JsonDocument doc = JsonDocument.Parse(response.Content); + string runId = doc.RootElement.GetProperty("id"u8).GetString()!; + + // Create the poller + ThreadRunPoller poller = new ThreadRunPoller( + _pipeline, _endpoint, result, threadId, runId, options); + + // Create the operation subclient + ThreadRunOperation operation = new ThreadRunOperation( + _pipeline, _endpoint, + threadId, runId, result.GetRawResponse(), + poller); + + if (returnWhen == ReturnWhen.Started) + { + return operation; + } + + operation.WaitForCompletionResult(); + return operation; + } - /// - public virtual ClientResult CancelRun(string threadId, string runId, RequestOptions options) - => _runSubClient.CancelRun(threadId, runId, options); + public virtual ThreadRunOperation CreateRun( + ReturnWhen returnWhen, + string threadId, + BinaryContent content, + RequestOptions options = null) + { + ClientResult result = _runSubClient.CreateRun(threadId, content, options); - /// - public virtual Task SubmitToolOutputsToRunAsync(string threadId, string runId, BinaryContent content, RequestOptions options = null) - => _runSubClient.SubmitToolOutputsToRunAsync(threadId, runId, content, options); + // Protocol level: get values needed to create subclient from response + PipelineResponse response = result.GetRawResponse(); + using JsonDocument doc = JsonDocument.Parse(response.Content); + string runId = doc.RootElement.GetProperty("id"u8).GetString()!; - /// - public virtual ClientResult SubmitToolOutputsToRun(string threadId, string runId, BinaryContent content, RequestOptions options = null) - => _runSubClient.SubmitToolOutputsToRun(threadId, runId, content, options); + // TODO: clean up poller and operation subclients per redundancy - /// - public virtual Task GetRunStepsAsync(string threadId, string runId, int? limit, string order, string after, string before, RequestOptions options) - => _runSubClient.GetRunStepsAsync(threadId, runId, limit, order, after, before, options); + // Create the poller + ThreadRunPoller poller = new ThreadRunPoller( + _pipeline, _endpoint, result, threadId, runId, options); - /// - public virtual ClientResult GetRunSteps(string threadId, string runId, int? limit, string order, string after, string before, RequestOptions options) - => _runSubClient.GetRunSteps(threadId, runId, limit, order, after, before, options); + // Create the operation subclient + ThreadRunOperation operation = new ThreadRunOperation( + _pipeline, _endpoint, + threadId, runId, result.GetRawResponse(), + poller); - /// - public virtual Task GetRunStepAsync(string threadId, string runId, string stepId, RequestOptions options) - => _runSubClient.GetRunStepAsync(threadId, runId, stepId, options); + if (returnWhen == ReturnWhen.Started) + { + return operation; + } - /// - public virtual ClientResult GetRunStep(string threadId, string runId, string stepId, RequestOptions options) - => _runSubClient.GetRunStep(threadId, runId, stepId, options); + operation.WaitForCompletionResult(); + return operation; + } /// public virtual Task CreateThreadAsync(BinaryContent content, RequestOptions options = null) diff --git a/src/Custom/Assistants/AssistantClient.cs b/src/Custom/Assistants/AssistantClient.cs index 155409142..0ae039437 100644 --- a/src/Custom/Assistants/AssistantClient.cs +++ b/src/Custom/Assistants/AssistantClient.cs @@ -3,11 +3,9 @@ using System.ClientModel.Primitives; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using static OpenAI.InternalListHelpers; namespace OpenAI.Assistants; @@ -105,31 +103,87 @@ public virtual ClientResult CreateAssistant(string model, AssistantCr /// /// Returns a collection of instances. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. - /// A collection of assistants that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetAssistantsAsync(ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + /// + public virtual AsyncPageCollection GetAssistantsAsync( + AssistantCollectionOptions options = default, + CancellationToken cancellationToken = default) { - return CreateAsyncPageable((continuationToken, pageSize) - => GetAssistantsAsync(pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + /// + /// Rehydrates a collection of instances a page token's serialized bytes. + /// + /// Serialized page token indicating the first page of the collection to rehydrate. + /// A token that can be used to cancel this method call. + /// + public virtual AsyncPageCollection GetAssistantsAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + AssistantsPageToken pageToken = AssistantsPageToken.FromToken(firstPageToken); + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); } /// /// Returns a collection of instances. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. + /// A token that can be used to cancel this method call. + /// + public virtual PageCollection GetAssistants( + AssistantCollectionOptions options = default, + CancellationToken cancellationToken = default) + { + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + /// + /// Rehydrates a collection of instances a page token's serialized bytes. + /// + /// Serialized page token indicating the first page of the collection to rehydrate. /// A token that can be used to cancel this method call. - /// A collection of assistants that can be enumerated using foreach. - public virtual PageableCollection GetAssistants(ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + /// + public virtual PageCollection GetAssistants( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) { - return CreatePageable((continuationToken, pageSize) - => GetAssistants(pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + AssistantsPageToken pageToken = AssistantsPageToken.FromToken(firstPageToken); + AssistantsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); } /// @@ -289,7 +343,7 @@ public virtual async Task> CreateMessageAsync( string threadId, MessageRole role, IEnumerable content, - MessageCreationOptions options = null, + MessageCreationOptions options = null, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); @@ -339,42 +393,86 @@ public virtual ClientResult CreateMessage( /// Returns a collection of instances from an existing . /// /// The ID of the thread to list messages from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. - /// A collection of messages that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetMessagesAsync( + /// + public virtual AsyncPageCollection GetMessagesAsync( string threadId, - ListOrder? resultOrder = null, + MessageCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - return CreateAsyncPageable((continuationToken, pageSize) - => GetMessagesAsync(threadId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, + threadId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual AsyncPageCollection GetMessagesAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + MessagesPageToken pageToken = MessagesPageToken.FromToken(firstPageToken); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); } /// /// Returns a collection of instances from an existing . /// /// The ID of the thread to list messages from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. - /// A collection of messages that can be enumerated using foreach. - public virtual PageableCollection GetMessages( + /// + public virtual PageCollection GetMessages( string threadId, - ListOrder? resultOrder = null, + MessageCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - return CreatePageable((continuationToken, pageSize) - => GetMessages(threadId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, + threadId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + public virtual PageCollection GetMessages( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + MessagesPageToken pageToken = MessagesPageToken.FromToken(firstPageToken); + MessagesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); } /// @@ -489,7 +587,12 @@ public virtual ClientResult DeleteMessage(string threadId, string messageI /// Additional options for the run. /// A token that can be used to cancel this method call. /// A new instance. - public virtual async Task> CreateRunAsync(string threadId, string assistantId, RunCreationOptions options = null, CancellationToken cancellationToken = default) + public virtual async Task CreateRunAsync( + ReturnWhen returnWhen, + string threadId, + string assistantId, + RunCreationOptions options = null, + CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); @@ -497,9 +600,7 @@ public virtual async Task> CreateRunAsync(string threadI options.AssistantId = assistantId; options.Stream = null; - ClientResult protocolResult = await CreateRunAsync(threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions()) - .ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + return await CreateRunAsync(returnWhen, threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions()).ConfigureAwait(false); } /// @@ -511,7 +612,12 @@ public virtual async Task> CreateRunAsync(string threadI /// Additional options for the run. /// A token that can be used to cancel this method call. /// A new instance. - public virtual ClientResult CreateRun(string threadId, string assistantId, RunCreationOptions options = null, CancellationToken cancellationToken = default) + public virtual ThreadRunOperation CreateRun( + ReturnWhen returnWhen, + string threadId, + string assistantId, + RunCreationOptions options = null, + CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); @@ -519,37 +625,36 @@ public virtual ClientResult CreateRun(string threadId, string assista options.AssistantId = assistantId; options.Stream = null; - ClientResult protocolResult = CreateRun(threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + return CreateRun(returnWhen, threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions()); } - /// - /// Begins a new streaming that evaluates a using a specified - /// . - /// - /// The ID of the thread that the run should evaluate. - /// The ID of the assistant that should be used when evaluating the thread. - /// Additional options for the run. - /// A token that can be used to cancel this method call. - public virtual AsyncResultCollection CreateRunStreamingAsync( - string threadId, - string assistantId, - RunCreationOptions options = null, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); + ///// + ///// Begins a new streaming that evaluates a using a specified + ///// . + ///// + ///// The ID of the thread that the run should evaluate. + ///// The ID of the assistant that should be used when evaluating the thread. + ///// Additional options for the run. + ///// A token that can be used to cancel this method call. + //public virtual AsyncCollectionResult CreateRunStreamingAsync( + // string threadId, + // string assistantId, + // RunCreationOptions options = null, + // CancellationToken cancellationToken = default) + //{ + // Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + // Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); - options ??= new(); - options.AssistantId = assistantId; - options.Stream = true; + // options ??= new(); + // options.AssistantId = assistantId; + // options.Stream = true; - async Task getResultAsync() => - await CreateRunAsync(threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions(streaming: true)) - .ConfigureAwait(false); + // async Task getResultAsync() => + // await CreateRunAsync(threadId, options.ToBinaryContent(), cancellationToken.ToRequestOptions(streaming: true)) + // .ConfigureAwait(false); - return new AsyncStreamingUpdateCollection(getResultAsync); - } + // return new AsyncStreamingUpdateCollection(getResultAsync); + //} /// /// Begins a new streaming that evaluates a using a specified @@ -559,7 +664,7 @@ await CreateRunAsync(threadId, options.ToBinaryContent(), cancellationToken.ToRe /// The ID of the assistant that should be used when evaluating the thread. /// Additional options for the run. /// A token that can be used to cancel this method call. - public virtual ResultCollection CreateRunStreaming( + public virtual CollectionResult CreateRunStreaming( string threadId, string assistantId, RunCreationOptions options = null, @@ -585,7 +690,8 @@ public virtual ResultCollection CreateRunStreaming( /// Additional options to apply to the run that will begin. /// A token that can be used to cancel this method call. /// A new . - public virtual async Task> CreateThreadAndRunAsync( + public virtual async Task CreateThreadAndRunAsync( + ReturnWhen returnWhen, string assistantId, ThreadCreationOptions threadOptions = null, RunCreationOptions runOptions = null, @@ -594,8 +700,7 @@ public virtual async Task> CreateThreadAndRunAsync( runOptions ??= new(); runOptions.Stream = null; BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); - ClientResult protocolResult = await CreateThreadAndRunAsync(protocolContent, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + return await CreateThreadAndRunAsync(returnWhen, protocolContent, cancellationToken.ToRequestOptions()).ConfigureAwait(false); } /// @@ -606,7 +711,8 @@ public virtual async Task> CreateThreadAndRunAsync( /// Additional options to apply to the run that will begin. /// A token that can be used to cancel this method call. /// A new . - public virtual ClientResult CreateThreadAndRun( + public virtual ThreadRunOperation CreateThreadAndRun( + ReturnWhen returnWhen, string assistantId, ThreadCreationOptions threadOptions = null, RunCreationOptions runOptions = null, @@ -615,343 +721,143 @@ public virtual ClientResult CreateThreadAndRun( runOptions ??= new(); runOptions.Stream = null; BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); - ClientResult protocolResult = CreateThreadAndRun(protocolContent, cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); - } - - /// - /// Creates a new thread and immediately begins a streaming run against it using the specified . - /// - /// The ID of the assistant that the new run should use. - /// Options for the new thread that will be created. - /// Additional options to apply to the run that will begin. - /// A token that can be used to cancel this method call. - public virtual AsyncResultCollection CreateThreadAndRunStreamingAsync( - string assistantId, - ThreadCreationOptions threadOptions = null, - RunCreationOptions runOptions = null, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); - - runOptions ??= new(); - runOptions.Stream = true; - BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); - - async Task getResultAsync() => - await CreateThreadAndRunAsync(protocolContent, cancellationToken.ToRequestOptions(streaming: true)) - .ConfigureAwait(false); - - return new AsyncStreamingUpdateCollection(getResultAsync); - } - - /// - /// Creates a new thread and immediately begins a streaming run against it using the specified . - /// - /// The ID of the assistant that the new run should use. - /// Options for the new thread that will be created. - /// Additional options to apply to the run that will begin. - /// A token that can be used to cancel this method call. - public virtual ResultCollection CreateThreadAndRunStreaming( - string assistantId, - ThreadCreationOptions threadOptions = null, - RunCreationOptions runOptions = null, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); - - runOptions ??= new(); - runOptions.Stream = true; - BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); - - ClientResult getResult() => CreateThreadAndRun(protocolContent, cancellationToken.ToRequestOptions(streaming: true)); - - return new StreamingUpdateCollection(getResult); - } - - /// - /// Returns a collection of instances associated with an existing . - /// - /// The ID of the thread that runs in the list should be associated with. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A token that can be used to cancel this method call. - /// A collection of runs that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetRunsAsync( - string threadId, - ListOrder? resultOrder = default, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - - return CreateAsyncPageable((continuationToken, pageSize) - => GetRunsAsync(threadId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); - } + return CreateThreadAndRun(returnWhen, protocolContent, cancellationToken.ToRequestOptions()); + } + + ///// + ///// Creates a new thread and immediately begins a streaming run against it using the specified . + ///// + ///// The ID of the assistant that the new run should use. + ///// Options for the new thread that will be created. + ///// Additional options to apply to the run that will begin. + ///// A token that can be used to cancel this method call. + //public virtual AsyncCollectionResult CreateThreadAndRunStreamingAsync( + // string assistantId, + // ThreadCreationOptions threadOptions = null, + // RunCreationOptions runOptions = null, + // CancellationToken cancellationToken = default) + //{ + // Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); + + // runOptions ??= new(); + // runOptions.Stream = true; + // BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); + + // async Task getResultAsync() => + // await CreateThreadAndRunAsync(protocolContent, cancellationToken.ToRequestOptions(streaming: true)) + // .ConfigureAwait(false); + + // return new AsyncStreamingUpdateCollection(getResultAsync); + //} + + ///// + ///// Creates a new thread and immediately begins a streaming run against it using the specified . + ///// + ///// The ID of the assistant that the new run should use. + ///// Options for the new thread that will be created. + ///// Additional options to apply to the run that will begin. + ///// A token that can be used to cancel this method call. + //public virtual CollectionResult CreateThreadAndRunStreaming( + // string assistantId, + // ThreadCreationOptions threadOptions = null, + // RunCreationOptions runOptions = null, + // CancellationToken cancellationToken = default) + //{ + // Argument.AssertNotNullOrEmpty(assistantId, nameof(assistantId)); + + // runOptions ??= new(); + // runOptions.Stream = true; + // BinaryContent protocolContent = CreateThreadAndRunProtocolContent(assistantId, threadOptions, runOptions); + + // ClientResult getResult() => CreateThreadAndRun(protocolContent, cancellationToken.ToRequestOptions(streaming: true)); + + // return new StreamingUpdateCollection(getResult); + //} /// /// Returns a collection of instances associated with an existing . /// /// The ID of the thread that runs in the list should be associated with. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. - /// A collection of runs that can be enumerated using foreach. - public virtual PageableCollection GetRuns( + /// + public virtual AsyncPageCollection GetRunsAsync( string threadId, - ListOrder? resultOrder = default, + RunCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - return CreatePageable((continuationToken, pageSize) - => GetRuns(threadId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); - } - - /// - /// Gets an existing from a known . - /// - /// The ID of the thread to retrieve the run from. - /// The ID of the run to retrieve. - /// A token that can be used to cancel this method call. - /// The existing instance. - public virtual async Task> GetRunAsync(string threadId, string runId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, + threadId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); - ClientResult protocolResult = await GetRunAsync(threadId, runId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + return PageCollectionHelpers.CreateAsync(enumerator); } - /// - /// Gets an existing from a known . - /// - /// The ID of the thread to retrieve the run from. - /// The ID of the run to retrieve. - /// A token that can be used to cancel this method call. - /// The existing instance. - public virtual ClientResult GetRun(string threadId, string runId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - - ClientResult protocolResult = GetRun(threadId, runId, cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); - } - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run. - /// - /// The thread ID of the thread being run. - /// The ID of the run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - /// A token that can be used to cancel this method call. - /// The , updated after the submission was processed. - public virtual async Task> SubmitToolOutputsToRunAsync( - string threadId, - string runId, - IEnumerable toolOutputs, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - - BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs).ToBinaryContent(); - ClientResult protocolResult = await SubmitToolOutputsToRunAsync(threadId, runId, content, cancellationToken.ToRequestOptions()) - .ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); - } - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run. - /// - /// The thread ID of the thread being run. - /// The ID of the run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - /// A token that can be used to cancel this method call. - /// The , updated after the submission was processed. - public virtual ClientResult SubmitToolOutputsToRun( - string threadId, - string runId, - IEnumerable toolOutputs, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - - BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs).ToBinaryContent(); - ClientResult protocolResult = SubmitToolOutputsToRun(threadId, runId, content, cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); - } - - /// - /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. - /// - /// The thread ID of the thread being run. - /// The ID of the run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// - /// A token that can be used to cancel this method call. - public virtual AsyncResultCollection SubmitToolOutputsToRunStreamingAsync( - string threadId, - string runId, - IEnumerable toolOutputs, + public virtual AsyncPageCollection GetRunsAsync( + ContinuationToken firstPageToken, CancellationToken cancellationToken = default) { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); - BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs.ToList(), stream: true, null) - .ToBinaryContent(); + RunsPageToken pageToken = RunsPageToken.FromToken(firstPageToken); + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); - async Task getResultAsync() => - await SubmitToolOutputsToRunAsync(threadId, runId, content, cancellationToken.ToRequestOptions(streaming: true)) - .ConfigureAwait(false); - - return new AsyncStreamingUpdateCollection(getResultAsync); + return PageCollectionHelpers.CreateAsync(enumerator); } /// - /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. + /// Returns a collection of instances associated with an existing . /// - /// The thread ID of the thread being run. - /// The ID of the run that reached a requires_action status. - /// - /// The tool outputs, corresponding to instances from the run. - /// + /// The ID of the thread that runs in the list should be associated with. + /// Options describing the collection to return. /// A token that can be used to cancel this method call. - public virtual ResultCollection SubmitToolOutputsToRunStreaming( + /// + public virtual PageCollection GetRuns( string threadId, - string runId, - IEnumerable toolOutputs, + RunCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - - BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs.ToList(), stream: true, null) - .ToBinaryContent(); - - ClientResult getResult() => SubmitToolOutputsToRun(threadId, runId, content, cancellationToken.ToRequestOptions(streaming: true)); - - return new StreamingUpdateCollection(getResult); - } - - /// - /// Cancels an in-progress . - /// - /// The ID of the thread associated with the run. - /// The ID of the run to cancel. - /// A token that can be used to cancel this method call. - /// An updated instance, reflecting the new status of the run. - public virtual async Task> CancelRunAsync(string threadId, string runId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - ClientResult protocolResult = await CancelRunAsync(threadId, runId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); - } + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, + threadId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); - /// - /// Cancels an in-progress . - /// - /// The ID of the thread associated with the run. - /// The ID of the run to cancel. - /// A token that can be used to cancel this method call. - /// An updated instance, reflecting the new status of the run. - public virtual ClientResult CancelRun(string threadId, string runId, CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); - - ClientResult protocolResult = CancelRun(threadId, runId, cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + return PageCollectionHelpers.Create(enumerator); } - /// - /// Gets a collection of instances associated with a . - /// - /// The ID of the thread associated with the run. - /// The ID of the run to list run steps from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A token that can be used to cancel this method call. - /// A collection of run steps that can be enumerated using await foreach. - public virtual AsyncPageableCollection GetRunStepsAsync( - string threadId, - string runId, - ListOrder? resultOrder = default, + public virtual PageCollection GetRuns( + ContinuationToken firstPageToken, CancellationToken cancellationToken = default) { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); - return CreateAsyncPageable((continuationToken, pageSize) - => GetRunStepsAsync(threadId, runId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); - } - - /// - /// Gets a collection of instances associated with a . - /// - /// The ID of the thread associated with the run. - /// The ID of the run to list run steps from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// A token that can be used to cancel this method call. - /// A collection of run steps that can be enumerated using foreach. - public virtual PageableCollection GetRunSteps( - string threadId, - string runId, - ListOrder? resultOrder = default, - CancellationToken cancellationToken = default) - { - Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); - Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + RunsPageToken pageToken = RunsPageToken.FromToken(firstPageToken); + RunsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); - return CreatePageable((continuationToken, pageSize) - => GetRunSteps(threadId, runId, pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); - } - - /// - /// Gets a single run step from a run. - /// - /// The ID of the thread associated with the run. - /// The ID of the run. - /// The ID of the run step. - /// A token that can be used to cancel this method call. - /// A instance corresponding to the specified step. - public virtual async Task> GetRunStepAsync(string threadId, string runId, string stepId, CancellationToken cancellationToken = default) - { - ClientResult protocolResult = await GetRunStepAsync(threadId, runId, stepId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return CreateResultFromProtocol(protocolResult, RunStep.FromResponse); - } - - /// - /// Gets a single run step from a run. - /// - /// The ID of the thread associated with the run. - /// The ID of the run. - /// The ID of the run step. - /// A token that can be used to cancel this method call. - /// A instance corresponding to the specified step. - public virtual ClientResult GetRunStep(string threadId, string runId, string stepId, CancellationToken cancellationToken = default) - { - ClientResult protocolResult = GetRunStep(threadId, runId, stepId, cancellationToken.ToRequestOptions()); - return CreateResultFromProtocol(protocolResult, RunStep.FromResponse); + return PageCollectionHelpers.Create(enumerator); } private static BinaryContent CreateThreadAndRunProtocolContent( diff --git a/src/Custom/Assistants/AssistantCollectionOptions.cs b/src/Custom/Assistants/AssistantCollectionOptions.cs new file mode 100644 index 000000000..d78696b94 --- /dev/null +++ b/src/Custom/Assistants/AssistantCollectionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.Assistants; + +public class AssistantCollectionOptions +{ + public AssistantCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } +} diff --git a/src/Custom/Assistants/MessageCollectionOptions.cs b/src/Custom/Assistants/MessageCollectionOptions.cs new file mode 100644 index 000000000..e4aa159fa --- /dev/null +++ b/src/Custom/Assistants/MessageCollectionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.Assistants; + +public class MessageCollectionOptions +{ + public MessageCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } +} diff --git a/src/Custom/Assistants/RunCollectionOptions.cs b/src/Custom/Assistants/RunCollectionOptions.cs new file mode 100644 index 000000000..53d503ba3 --- /dev/null +++ b/src/Custom/Assistants/RunCollectionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.Assistants; + +public class RunCollectionOptions +{ + public RunCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } +} diff --git a/src/Custom/Assistants/RunStepCollectionOptions.cs b/src/Custom/Assistants/RunStepCollectionOptions.cs new file mode 100644 index 000000000..aeb3d9813 --- /dev/null +++ b/src/Custom/Assistants/RunStepCollectionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.Assistants; + +public class RunStepCollectionOptions +{ + public RunStepCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } +} diff --git a/src/Custom/Assistants/Streaming/AsyncStreamingUpdateCollection.cs b/src/Custom/Assistants/Streaming/AsyncStreamingUpdateCollection.cs index c7640f02f..187163a09 100644 --- a/src/Custom/Assistants/Streaming/AsyncStreamingUpdateCollection.cs +++ b/src/Custom/Assistants/Streaming/AsyncStreamingUpdateCollection.cs @@ -15,7 +15,7 @@ namespace OpenAI.Assistants; /// /// Implementation of collection abstraction over streaming assistant updates. /// -internal class AsyncStreamingUpdateCollection : AsyncResultCollection +internal class AsyncStreamingUpdateCollection : AsyncCollectionResult { private readonly Func> _getResultAsync; diff --git a/src/Custom/Assistants/Streaming/StreamingUpdateCollection.cs b/src/Custom/Assistants/Streaming/StreamingUpdateCollection.cs index 85f51273e..954bc2284 100644 --- a/src/Custom/Assistants/Streaming/StreamingUpdateCollection.cs +++ b/src/Custom/Assistants/Streaming/StreamingUpdateCollection.cs @@ -13,7 +13,7 @@ namespace OpenAI.Assistants; /// /// Implementation of collection abstraction over streaming assistant updates. /// -internal class StreamingUpdateCollection : ResultCollection +internal class StreamingUpdateCollection : CollectionResult { private readonly Func _getResult; diff --git a/src/Custom/Chat/ChatClient.cs b/src/Custom/Chat/ChatClient.cs index 0c509b393..72b9a5d7d 100644 --- a/src/Custom/Chat/ChatClient.cs +++ b/src/Custom/Chat/ChatClient.cs @@ -132,7 +132,7 @@ public virtual ClientResult CompleteChat(params ChatMessage[] me /// Additional options for the chat completion request. /// A token that can be used to cancel this method call. /// A streaming result with incremental chat completion updates. - public virtual AsyncResultCollection CompleteChatStreamingAsync(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default) + public virtual AsyncCollectionResult CompleteChatStreamingAsync(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default) { Argument.AssertNotNull(messages, nameof(messages)); @@ -156,7 +156,7 @@ async Task getResultAsync() => /// /// The messages to provide as input for chat completion. /// A streaming result with incremental chat completion updates. - public virtual AsyncResultCollection CompleteChatStreamingAsync(params ChatMessage[] messages) + public virtual AsyncCollectionResult CompleteChatStreamingAsync(params ChatMessage[] messages) => CompleteChatStreamingAsync(messages, default(ChatCompletionOptions)); /// @@ -171,7 +171,7 @@ public virtual AsyncResultCollection CompleteChat /// Additional options for the chat completion request. /// A token that can be used to cancel this method call. /// A streaming result with incremental chat completion updates. - public virtual ResultCollection CompleteChatStreaming(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default) + public virtual CollectionResult CompleteChatStreaming(IEnumerable messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default) { Argument.AssertNotNull(messages, nameof(messages)); @@ -193,7 +193,7 @@ public virtual ResultCollection CompleteChatStrea /// /// The messages to provide as input for chat completion. /// A streaming result with incremental chat completion updates. - public virtual ResultCollection CompleteChatStreaming(params ChatMessage[] messages) + public virtual CollectionResult CompleteChatStreaming(params ChatMessage[] messages) => CompleteChatStreaming(messages, default(ChatCompletionOptions)); private void CreateChatCompletionOptions(IEnumerable messages, ref ChatCompletionOptions options, bool stream = false) diff --git a/src/Custom/Chat/Internal/AsyncStreamingChatCompletionUpdateCollection.cs b/src/Custom/Chat/Internal/AsyncStreamingChatCompletionUpdateCollection.cs index 55bbeb7d8..db3781235 100644 --- a/src/Custom/Chat/Internal/AsyncStreamingChatCompletionUpdateCollection.cs +++ b/src/Custom/Chat/Internal/AsyncStreamingChatCompletionUpdateCollection.cs @@ -15,7 +15,7 @@ namespace OpenAI.Chat; /// /// Implementation of collection abstraction over streaming chat updates. /// -internal class AsyncStreamingChatCompletionUpdateCollection : AsyncResultCollection +internal class AsyncStreamingChatCompletionUpdateCollection : AsyncCollectionResult { private readonly Func> _getResultAsync; diff --git a/src/Custom/Chat/Internal/StreamingChatCompletionUpdateCollection.cs b/src/Custom/Chat/Internal/StreamingChatCompletionUpdateCollection.cs index 0e51009e7..647642c77 100644 --- a/src/Custom/Chat/Internal/StreamingChatCompletionUpdateCollection.cs +++ b/src/Custom/Chat/Internal/StreamingChatCompletionUpdateCollection.cs @@ -14,7 +14,7 @@ namespace OpenAI.Chat; /// /// Implementation of collection abstraction over streaming chat updates. /// -internal class StreamingChatCompletionUpdateCollection : ResultCollection +internal class StreamingChatCompletionUpdateCollection : CollectionResult { private readonly Func _getResult; diff --git a/src/Custom/Common/InternalListHelpers.cs b/src/Custom/Common/InternalListHelpers.cs deleted file mode 100644 index 4537ad780..000000000 --- a/src/Custom/Common/InternalListHelpers.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace OpenAI; - -internal static class InternalListHelpers -{ - internal delegate Task AsyncListResponseFunc(string continuationToken, int? pageSize); - internal delegate ClientResult ListResponseFunc(string continuationToken, int? pageSize); - - internal static AsyncPageableCollection CreateAsyncPageable(AsyncListResponseFunc listResponseFunc) - where U : IJsonModel, IInternalListResponse - { - async Task> pageFunc(string continuationToken, int? pageSize) - => GetPageFromProtocol(await listResponseFunc(continuationToken, pageSize).ConfigureAwait(false)); - return PageableResultHelpers.Create((pageSize) => pageFunc(null, pageSize), pageFunc); - } - - internal static PageableCollection CreatePageable(ListResponseFunc listResponseFunc) - where U : IJsonModel, IInternalListResponse - { - ResultPage pageFunc(string continuationToken, int? pageSize) - => GetPageFromProtocol(listResponseFunc(continuationToken, pageSize)); - return PageableResultHelpers.Create((pageSize) => pageFunc(null, pageSize), pageFunc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ResultPage GetPageFromProtocol(ClientResult protocolResult) - where UInternalList : IJsonModel, IInternalListResponse - { - PipelineResponse response = protocolResult.GetRawResponse(); - IInternalListResponse values = ModelReaderWriter.Read(response.Content); - return ResultPage.Create(values.Data, values.HasMore ? values.LastId : null, response); - } -} diff --git a/src/Custom/Common/PageableResultHelpers.cs b/src/Custom/Common/PageableResultHelpers.cs deleted file mode 100644 index 23290c932..000000000 --- a/src/Custom/Common/PageableResultHelpers.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.ClientModel; -using System.Collections.Generic; -using System.Threading.Tasks; - -#nullable enable - -namespace OpenAI; - -internal class PageableResultHelpers -{ - public static PageableCollection Create(Func> firstPageFunc, Func>? nextPageFunc, int? pageSize = default) where T : notnull - { - ResultPage first(string? _, int? pageSizeHint) => firstPageFunc(pageSizeHint); - return new FuncPageable(first, nextPageFunc, pageSize); - } - - public static AsyncPageableCollection Create(Func>> firstPageFunc, Func>>? nextPageFunc, int? pageSize = default) where T : notnull - { - Task> first(string? _, int? pageSizeHint) => firstPageFunc(pageSizeHint); - return new FuncAsyncPageable(first, nextPageFunc, pageSize); - } - - private class FuncAsyncPageable : AsyncPageableCollection where T : notnull - { - private readonly Func>> _firstPageFunc; - private readonly Func>>? _nextPageFunc; - private readonly int? _defaultPageSize; - - public FuncAsyncPageable(Func>> firstPageFunc, Func>>? nextPageFunc, int? defaultPageSize = default) - { - _firstPageFunc = firstPageFunc; - _nextPageFunc = nextPageFunc; - _defaultPageSize = defaultPageSize; - } - - public override async IAsyncEnumerable> AsPages(string? continuationToken = default, int? pageSizeHint = default) - { - Func>>? pageFunc = string.IsNullOrEmpty(continuationToken) ? _firstPageFunc : _nextPageFunc; - - if (pageFunc == null) - { - yield break; - } - - int? pageSize = pageSizeHint ?? _defaultPageSize; - do - { - ResultPage page = await pageFunc(continuationToken, pageSize).ConfigureAwait(false); - SetRawResponse(page.GetRawResponse()); - yield return page; - continuationToken = page.ContinuationToken; - pageFunc = _nextPageFunc; - } - while (!string.IsNullOrEmpty(continuationToken) && pageFunc != null); - } - } - - private class FuncPageable : PageableCollection where T : notnull - { - private readonly Func> _firstPageFunc; - private readonly Func>? _nextPageFunc; - private readonly int? _defaultPageSize; - - public FuncPageable(Func> firstPageFunc, Func>? nextPageFunc, int? defaultPageSize = default) - { - _firstPageFunc = firstPageFunc; - _nextPageFunc = nextPageFunc; - _defaultPageSize = defaultPageSize; - } - - public override IEnumerable> AsPages(string? continuationToken = default, int? pageSizeHint = default) - { - Func>? pageFunc = string.IsNullOrEmpty(continuationToken) ? _firstPageFunc : _nextPageFunc; - - if (pageFunc == null) - { - yield break; - } - - int? pageSize = pageSizeHint ?? _defaultPageSize; - do - { - ResultPage page = pageFunc(continuationToken, pageSize); - SetRawResponse(page.GetRawResponse()); - yield return page; - continuationToken = page.ContinuationToken; - pageFunc = _nextPageFunc; - } - while (!string.IsNullOrEmpty(continuationToken) && pageFunc != null); - } - } -} diff --git a/src/Custom/VectorStores/VectorStoreClient.Convenience.cs b/src/Custom/VectorStores/VectorStoreClient.Convenience.cs index 3917b5171..886da3e5d 100644 --- a/src/Custom/VectorStores/VectorStoreClient.Convenience.cs +++ b/src/Custom/VectorStores/VectorStoreClient.Convenience.cs @@ -2,6 +2,7 @@ using System.ClientModel; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace OpenAI.VectorStores; @@ -87,22 +88,15 @@ public virtual ClientResult AddFileToVectorStore(Vec /// /// The vector store to enumerate the file associations of. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// /// A collection of instances that can be asynchronously enumerated via /// await foreach. /// - public virtual AsyncPageableCollection GetFileAssociationsAsync( + public virtual AsyncPageCollection GetFileAssociationsAsync( VectorStore vectorStore, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null) - => GetFileAssociationsAsync(vectorStore?.Id, resultOrder, filter); + VectorStoreFileAssociationCollectionOptions options = default) + => GetFileAssociationsAsync(vectorStore?.Id, options); /// /// Gets the collection of instances representing file inclusions in the @@ -111,22 +105,15 @@ public virtual AsyncPageableCollection GetFileAssoci /// /// The ID vector store to enumerate the file associations of. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// /// A collection of instances that can be synchronously enumerated via /// foreach. /// - public virtual PageableCollection GetFileAssociations( + public virtual PageCollection GetFileAssociations( VectorStore vectorStore, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null) - => GetFileAssociations(vectorStore?.Id, resultOrder); + VectorStoreFileAssociationCollectionOptions options = default) + => GetFileAssociations(vectorStore?.Id, options); /// /// Gets a instance representing an existing association between a known @@ -232,43 +219,30 @@ public virtual ClientResult CancelBatchFileJob(VectorSt /// Gets the collection of file associations associated with a vector store batch file job, representing the files /// that were scheduled for ingestion into the vector store. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// The vector store batch file job to retrieve file associations from. + /// Options describing the collection to return. /// /// A collection of instances that can be asynchronously enumerated via /// await foreach. /// - public virtual AsyncPageableCollection GetFileAssociationsAsync( + public virtual AsyncPageCollection GetFileAssociationsAsync( VectorStoreBatchFileJob batchJob, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null) - => GetFileAssociationsAsync(batchJob?.VectorStoreId, batchJob?.BatchId, resultOrder, filter); + VectorStoreFileAssociationCollectionOptions options = default) + => GetFileAssociationsAsync(batchJob?.VectorStoreId, batchJob?.BatchId, options); /// /// Gets the collection of file associations associated with a vector store batch file job, representing the files /// that were scheduled for ingestion into the vector store. /// /// The vector store batch file job to retrieve file associations from. - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// /// A collection of instances that can be synchronously enumerated via /// foreach. /// - public virtual PageableCollection GetFileAssociations( + public virtual PageCollection GetFileAssociations( VectorStoreBatchFileJob batchJob, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null) - => GetFileAssociations(batchJob?.VectorStoreId, batchJob?.BatchId, resultOrder, filter); + VectorStoreFileAssociationCollectionOptions options = default) + => GetFileAssociations(batchJob?.VectorStoreId, batchJob?.BatchId, options); } diff --git a/src/Custom/VectorStores/VectorStoreClient.Protocol.cs b/src/Custom/VectorStores/VectorStoreClient.Protocol.cs index 311be206b..bd42ae360 100644 --- a/src/Custom/VectorStores/VectorStoreClient.Protocol.cs +++ b/src/Custom/VectorStores/VectorStoreClient.Protocol.cs @@ -1,7 +1,10 @@ -using System; +using OpenAI.Assistants; +using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Collections.Generic; using System.ComponentModel; +using System.Threading; using System.Threading.Tasks; namespace OpenAI.VectorStores; @@ -49,10 +52,10 @@ public partial class VectorStoreClient /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task GetVectorStoresAsync(int? limit, string order, string after, string before, RequestOptions options) + public virtual IAsyncEnumerable GetVectorStoresAsync(int? limit, string order, string after, string before, RequestOptions options) { - using PipelineMessage message = CreateGetVectorStoresRequest(limit, order, after, before, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PageResultEnumerator enumerator = new VectorStoresPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -80,10 +83,10 @@ public virtual async Task GetVectorStoresAsync(int? limit, string /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetVectorStores(int? limit, string order, string after, string before, RequestOptions options) + public virtual IEnumerable GetVectorStores(int? limit, string order, string after, string before, RequestOptions options) { - using PipelineMessage message = CreateGetVectorStoresRequest(limit, order, after, before, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PageResultEnumerator enumerator = new VectorStoresPageEnumerator(_pipeline, _endpoint, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); } /// @@ -256,12 +259,12 @@ public virtual ClientResult DeleteVectorStore(string vectorStoreId, RequestOptio /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task GetFileAssociationsAsync(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options) + public virtual IAsyncEnumerable GetFileAssociationsAsync(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - using PipelineMessage message = CreateGetVectorStoreFilesRequest(vectorStoreId, limit, order, after, before, filter, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PageResultEnumerator enumerator = new VectorStoreFilesPageEnumerator(_pipeline, _endpoint, vectorStoreId, limit, order, after, before, filter, options); + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -293,12 +296,12 @@ public virtual async Task GetFileAssociationsAsync(string vectorSt /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetFileAssociations(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options) + public virtual IEnumerable GetFileAssociations(string vectorStoreId, int? limit, string order, string after, string before, string filter, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - using PipelineMessage message = CreateGetVectorStoreFilesRequest(vectorStoreId, limit, order, after, before, filter, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PageResultEnumerator enumerator = new MessagesPageEnumerator(_pipeline, _endpoint, vectorStoreId, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); } /// @@ -571,13 +574,13 @@ public virtual ClientResult CancelBatchFileJob(string vectorStoreId, string batc /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) + public virtual IAsyncEnumerable GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - using PipelineMessage message = CreateGetFilesInVectorStoreBatchesRequest(vectorStoreId, batchId, limit, order, after, before, filter, options); - return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + PageResultEnumerator enumerator = new VectorStoreFileBatchesPageEnumerator(_pipeline, _endpoint, vectorStoreId, batchId, limit, order, after, before, filter, options); + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -610,12 +613,12 @@ public virtual async Task GetFileAssociationsAsync(string vectorSt /// Service returned a non-success status code. /// The response returned from the service. [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetFileAssociations(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) + public virtual IEnumerable GetFileAssociations(string vectorStoreId, string batchId, int? limit, string order, string after, string before, string filter, RequestOptions options) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); - using PipelineMessage message = CreateGetFilesInVectorStoreBatchesRequest(vectorStoreId, batchId, limit, order, after, before, filter, options); - return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + PageResultEnumerator enumerator = new VectorStoreFileBatchesPageEnumerator(_pipeline, _endpoint, vectorStoreId, batchId, limit, order, after, before, filter, options); + return PageCollectionHelpers.Create(enumerator); } } diff --git a/src/Custom/VectorStores/VectorStoreClient.cs b/src/Custom/VectorStores/VectorStoreClient.cs index e12c35737..c8c3c4308 100644 --- a/src/Custom/VectorStores/VectorStoreClient.cs +++ b/src/Custom/VectorStores/VectorStoreClient.cs @@ -1,14 +1,11 @@ -using OpenAI.Assistants; -using OpenAI.Files; +using OpenAI.Files; using System; using System.ClientModel; using System.ClientModel.Primitives; -using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; -using static OpenAI.InternalListHelpers; namespace OpenAI.VectorStores; @@ -52,7 +49,7 @@ public VectorStoreClient(ApiKeyCredential credential, OpenAIClientOptions option OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(credential, requireExplicitCredential: true), options), OpenAIClient.GetEndpoint(options), options) - {} + { } /// /// Initializes a new instance of that will use an API key from the OPENAI_API_KEY @@ -69,7 +66,7 @@ public VectorStoreClient(OpenAIClientOptions options = null) OpenAIClient.CreatePipeline(OpenAIClient.GetApiKey(), options), OpenAIClient.GetEndpoint(options), options) - {} + { } /// Initializes a new instance of VectorStoreClient. /// The HTTP pipeline for sending and receiving REST requests and responses. @@ -139,36 +136,80 @@ public virtual ClientResult DeleteVectorStore(string vectorStoreId, Cancel /// /// Gets the collection of instances for the configured organization. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be asynchronously enumerated via /// await foreach. /// - public virtual AsyncPageableCollection GetVectorStoresAsync(ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + public virtual AsyncPageCollection GetVectorStoresAsync( + VectorStoreCollectionOptions options = default, + CancellationToken cancellationToken = default) { - return CreateAsyncPageable((continuationToken, pageSize) - => GetVectorStoresAsync(pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual AsyncPageCollection GetVectorStoresAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoresPageToken pageToken = VectorStoresPageToken.FromToken(firstPageToken); + VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); } /// /// Gets the collection of instances for the configured organization. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be synchronously enumerated via foreach. /// - public virtual PageableCollection GetVectorStores(ListOrder? resultOrder = null, CancellationToken cancellationToken = default) + public virtual PageCollection GetVectorStores( + VectorStoreCollectionOptions options = default, + CancellationToken cancellationToken = default) { - return CreatePageable((continuationToken, pageSize) - => GetVectorStores(pageSize, resultOrder?.ToString(), continuationToken, null, cancellationToken.ToRequestOptions())); + VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + public virtual PageCollection GetVectorStores( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoresPageToken pageToken = VectorStoresPageToken.FromToken(firstPageToken); + VectorStoresPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); } /// @@ -214,28 +255,48 @@ public virtual ClientResult AddFileToVectorStore(str /// /// The ID of the vector store to enumerate the file associations of. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be asynchronously enumerated via /// await foreach. /// - public virtual AsyncPageableCollection GetFileAssociationsAsync( + public virtual AsyncPageCollection GetFileAssociationsAsync( string vectorStoreId, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null, + VectorStoreFileAssociationCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - return CreateAsyncPageable( - (continuationToken, pageSize) => GetFileAssociationsAsync( - vectorStoreId, pageSize, resultOrder?.ToString(), continuationToken, null, filter?.ToString(), cancellationToken.ToRequestOptions())); + + VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, + vectorStoreId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + options?.Filter?.ToString(), + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual AsyncPageCollection GetFileAssociationsAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromToken(firstPageToken); + VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.VectorStoreId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + pageToken.Filter, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -245,24 +306,48 @@ public virtual AsyncPageableCollection GetFileAssoci /// /// The ID of the vector store to enumerate the file associations of. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be synchronously enumerated via /// foreach. /// - public virtual PageableCollection GetFileAssociations(string vectorStoreId, ListOrder? resultOrder = null, VectorStoreFileStatusFilter? filter = null, CancellationToken cancellationToken = default) + public virtual PageCollection GetFileAssociations( + string vectorStoreId, + VectorStoreFileAssociationCollectionOptions options = default, + CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); - return CreatePageable( - (continuationToken, pageSize) => GetFileAssociations( - vectorStoreId, pageSize, resultOrder?.ToString(), continuationToken, null, filter?.ToString(), cancellationToken.ToRequestOptions())); + + VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, + vectorStoreId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + options?.Filter?.ToString(), + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + public virtual PageCollection GetFileAssociations( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromToken(firstPageToken); + VectorStoreFilesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.VectorStoreId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + pageToken.Filter, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); } /// @@ -275,7 +360,7 @@ public virtual PageableCollection GetFileAssociation /// A instance. public virtual async Task> GetFileAssociationAsync( string vectorStoreId, - string fileId, + string fileId, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); @@ -297,7 +382,7 @@ public virtual async Task> GetFileAssoc /// A instance. public virtual ClientResult GetFileAssociation( string vectorStoreId, - string fileId, + string fileId, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); @@ -475,31 +560,69 @@ public virtual ClientResult CancelBatchFileJob(string v /// /// The ID of the batch file job that was previously scheduled. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be asynchronously enumerated via /// await foreach. /// - public virtual AsyncPageableCollection GetFileAssociationsAsync( + public virtual AsyncPageCollection GetFileAssociationsAsync( string vectorStoreId, string batchJobId, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null, + VectorStoreFileAssociationCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - return CreateAsyncPageable( - (continuationToken, pageSize) => GetFileAssociationsAsync - (vectorStoreId, batchJobId, pageSize, resultOrder?.ToString(), continuationToken, null, filter?.ToString(), cancellationToken.ToRequestOptions())); + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, + vectorStoreId, + batchJobId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + options?.Filter?.ToString(), + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual AsyncPageCollection GetFileAssociationsAsync( + string vectorStoreId, + string batchJobId, + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); + + if (vectorStoreId != pageToken.VectorStoreId) + { + throw new ArgumentException( + "Invalid page token. 'vectorStoreId' value does not match page token value.", + nameof(vectorStoreId)); + } + + if (batchJobId != pageToken.BatchId) + { + throw new ArgumentException( + "Invalid page token. 'batchJobId' value does not match page token value.", + nameof(vectorStoreId)); + } + + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.VectorStoreId, + pageToken.BatchId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + pageToken.Filter, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); } /// @@ -512,30 +635,68 @@ public virtual AsyncPageableCollection GetFileAssoci /// /// The ID of the batch file job that was previously scheduled. /// - /// - /// The order that results should appear in the list according to their created_at - /// timestamp. - /// - /// - /// A status filter that file associations must match to be included in the collection. - /// + /// Options describing the collection to return. /// A token that can be used to cancel this method call. /// /// A collection of instances that can be synchronously enumerated via /// foreach. /// - public virtual PageableCollection GetFileAssociations( + public virtual PageCollection GetFileAssociations( string vectorStoreId, string batchJobId, - ListOrder? resultOrder = null, - VectorStoreFileStatusFilter? filter = null, + VectorStoreFileAssociationCollectionOptions options = default, CancellationToken cancellationToken = default) { Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); Argument.AssertNotNullOrEmpty(batchJobId, nameof(batchJobId)); - return CreatePageable( - (continuationToken, pageSize) => GetFileAssociations - (vectorStoreId, batchJobId, pageSize, resultOrder?.ToString(), continuationToken, null, filter?.ToString(), cancellationToken.ToRequestOptions())); + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, + vectorStoreId, + batchJobId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + options?.Filter?.ToString(), + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + public virtual PageCollection GetFileAssociations( + string vectorStoreId, + string batchJobId, + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + VectorStoreFileBatchesPageToken pageToken = VectorStoreFileBatchesPageToken.FromToken(firstPageToken); + + if (vectorStoreId != pageToken.VectorStoreId) + { + throw new ArgumentException( + "Invalid page token. 'vectorStoreId' value does not match page token value.", + nameof(vectorStoreId)); + } + + if (batchJobId != pageToken.BatchId) + { + throw new ArgumentException( + "Invalid page token. 'batchJobId' value does not match page token value.", + nameof(vectorStoreId)); + } + + VectorStoreFileBatchesPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.VectorStoreId, + pageToken.BatchId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + pageToken.Filter, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); } } diff --git a/src/Custom/VectorStores/VectorStoreCollectionOptions.cs b/src/Custom/VectorStores/VectorStoreCollectionOptions.cs new file mode 100644 index 000000000..2361aebde --- /dev/null +++ b/src/Custom/VectorStores/VectorStoreCollectionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.VectorStores; + +public class VectorStoreCollectionOptions +{ + public VectorStoreCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } +} diff --git a/src/Custom/VectorStores/VectorStoreFileAssociationCollectionOptions.cs b/src/Custom/VectorStores/VectorStoreFileAssociationCollectionOptions.cs new file mode 100644 index 000000000..0b8bb697d --- /dev/null +++ b/src/Custom/VectorStores/VectorStoreFileAssociationCollectionOptions.cs @@ -0,0 +1,37 @@ +using OpenAI.Files; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenAI.VectorStores; + +public class VectorStoreFileAssociationCollectionOptions +{ + public VectorStoreFileAssociationCollectionOptions() { } + + /// + /// The order that results should appear in the list according to + /// their created_at timestamp. + /// + public ListOrder? Order { get; init; } + + /// + /// The number of values to return in a page result. + /// + public int? PageSize { get; init; } + + /// + /// The id of the item preceeding the first item in the collection. + /// + public string AfterId { get; init; } + + /// + /// The id of the item following the last item in the collection. + /// + public string BeforeId { get; init; } + + /// + /// A status filter that file associations must match to be included in the collection. + /// + public VectorStoreFileStatusFilter? Filter { get; init; } +} diff --git a/src/OpenAI.csproj b/src/OpenAI.csproj index 8f61f3af6..125e5032b 100644 --- a/src/OpenAI.csproj +++ b/src/OpenAI.csproj @@ -60,7 +60,7 @@ - + diff --git a/src/To.Be.Generated/AssistantsPageEnumerator.cs b/src/To.Be.Generated/AssistantsPageEnumerator.cs new file mode 100644 index 000000000..f18521c1e --- /dev/null +++ b/src/To.Be.Generated/AssistantsPageEnumerator.cs @@ -0,0 +1,136 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +internal partial class AssistantsPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly int? _limit; + private readonly string _order; + + // Note: this one is special + private string _after; + + private readonly string _before; + private readonly RequestOptions _options; + + public AssistantsPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + int? limit, string order, string after, string before, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _limit = limit; + _order = order; + _after = after; + _before = before; + _options = options; + } + + public override async Task GetFirstAsync() + => await GetAssistantsPageAsync(_limit, _order, _after, _before, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetAssistantsPage(_limit, _order, _after, _before, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetAssistantsPageAsync(_limit, _order, _after, _before, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetAssistantsPage(_limit, _order, _after, _before, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListAssistantsResponse list = ModelReaderWriter.Read(response.Content)!; + + AssistantsPageToken pageToken = AssistantsPageToken.FromOptions(_limit, _order, _after, _before); + AssistantsPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + + // Note: these are the protocol methods - they are generated here + internal virtual async Task GetAssistantsPageAsync(int? limit, string order, string after, string before, RequestOptions options) + { + using PipelineMessage message = CreateGetAssistantsRequest(limit, order, after, before, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetAssistantsPage(int? limit, string order, string after, string before, RequestOptions options) + { + using PipelineMessage message = CreateGetAssistantsRequest(limit, order, after, before, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + private PipelineMessage CreateGetAssistantsRequest(int? limit, string order, string after, string before, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/assistants", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/AssistantsPageToken.cs b/src/To.Be.Generated/AssistantsPageToken.cs new file mode 100644 index 000000000..251e8a421 --- /dev/null +++ b/src/To.Be.Generated/AssistantsPageToken.cs @@ -0,0 +1,142 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Assistants; + +internal class AssistantsPageToken : ContinuationToken +{ + protected AssistantsPageToken(int? limit, string? order, string? after, string? before) + { + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public AssistantsPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new AssistantsPageToken(Limit, Order, After, Before); + } + + public static AssistantsPageToken FromToken(ContinuationToken token) + { + if (token is AssistantsPageToken pageToken) + { + return pageToken; + } + + BinaryData data = token.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create AssistantsPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + return new(limit, order, after, before); + } + + public static AssistantsPageToken FromOptions(int? limit, string? order, string? after, string? before) + => new AssistantsPageToken(limit, order, after, before); +} \ No newline at end of file diff --git a/src/To.Be.Generated/Internal/OperationPoller.cs b/src/To.Be.Generated/Internal/OperationPoller.cs new file mode 100644 index 000000000..6efd2bdc9 --- /dev/null +++ b/src/To.Be.Generated/Internal/OperationPoller.cs @@ -0,0 +1,23 @@ +using System.ClientModel; + +#nullable enable + +namespace OpenAI; + +// Convenience version +// Note: Right now, this just inherits from the protocol poller and adds a T. +// Do we need more than that? +internal abstract class OperationPoller : OperationResultPoller +{ + private T? _value; + + protected OperationPoller(ClientResult current) : base(current) + { + } + + public T Value => _value ??= GetValueFromResult(Current); + + public abstract T GetValueFromResult(ClientResult result); + + protected override void Update() => _value = GetValueFromResult(Current); +} diff --git a/src/To.Be.Generated/Internal/OperationResultPoller.cs b/src/To.Be.Generated/Internal/OperationResultPoller.cs new file mode 100644 index 000000000..98b22a059 --- /dev/null +++ b/src/To.Be.Generated/Internal/OperationResultPoller.cs @@ -0,0 +1,66 @@ +using System.ClientModel; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +// Protocol version +// Note: idea here is that this type is generated to be specific to the client. +// Like PageResultEnumerator, the subtype will be generated and wrapped in an +// outer public type. +internal abstract class OperationResultPoller +{ + private const int DefaultWaitMilliseconds = 1000; + + protected OperationResultPoller(ClientResult current) + { + Current = current; + } + + // TODO: Thread-safe assignment? + public ClientResult Current { get; protected set; } + + // Service-specific methods to be generated on the subclient + public abstract Task UpdateStatusAsync(); + + public abstract ClientResult UpdateStatus(); + + public abstract bool HasStopped(ClientResult result); + + // TODO: how does RequestOptions/CancellationToken work? + public async Task WaitForCompletionAsync() + { + bool hasStopped = HasStopped(Current); + + while (!hasStopped) + { + // TODO: implement an interesting wait routine + await Task.Delay(DefaultWaitMilliseconds); + + Current = await UpdateStatusAsync().ConfigureAwait(false); + Update(); + + hasStopped = HasStopped(Current); + } + } + + public void WaitForCompletion() + { + bool hasStopped = HasStopped(Current); + + while (!hasStopped) + { + // TODO: implement an interesting wait routine + Thread.Sleep(DefaultWaitMilliseconds); + + Current = UpdateStatus(); + Update(); + + hasStopped = HasStopped(Current); + } + } + + protected virtual void Update() { } +} \ No newline at end of file diff --git a/src/To.Be.Generated/Internal/PageCollectionHelpers.cs b/src/To.Be.Generated/Internal/PageCollectionHelpers.cs new file mode 100644 index 000000000..5cac0d7d6 --- /dev/null +++ b/src/To.Be.Generated/Internal/PageCollectionHelpers.cs @@ -0,0 +1,66 @@ +using System; +using System.ClientModel; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +internal class PageCollectionHelpers +{ + public static PageCollection Create(PageEnumerator enumerator) + => new EnumeratorPageCollection(enumerator); + + public static AsyncPageCollection CreateAsync(PageEnumerator enumerator) + => new AsyncEnumeratorPageCollection(enumerator); + + public static IEnumerable Create(PageResultEnumerator enumerator) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + } + + public static async IAsyncEnumerable CreateAsync(PageResultEnumerator enumerator) + { + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield return enumerator.Current; + } + } + + private class EnumeratorPageCollection : PageCollection + { + private readonly PageEnumerator _enumerator; + + public EnumeratorPageCollection(PageEnumerator enumerator) + { + _enumerator = enumerator; + } + + protected override PageResult GetCurrentPageCore() + => _enumerator.GetCurrentPage(); + + protected override IEnumerator> GetEnumeratorCore() + => _enumerator; + } + + private class AsyncEnumeratorPageCollection : AsyncPageCollection + { + private readonly PageEnumerator _enumerator; + + public AsyncEnumeratorPageCollection(PageEnumerator enumerator) + { + _enumerator = enumerator; + } + + protected override async Task> GetCurrentPageAsyncCore() + => await _enumerator.GetCurrentPageAsync().ConfigureAwait(false); + + protected override IAsyncEnumerator> GetAsyncEnumeratorCore(CancellationToken cancellationToken = default) + => _enumerator; + } +} diff --git a/src/To.Be.Generated/Internal/PageEnumerator.cs b/src/To.Be.Generated/Internal/PageEnumerator.cs new file mode 100644 index 000000000..6bf35810e --- /dev/null +++ b/src/To.Be.Generated/Internal/PageEnumerator.cs @@ -0,0 +1,60 @@ +using System.ClientModel; +using System.Collections.Generic; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +internal abstract class PageEnumerator : PageResultEnumerator, + IAsyncEnumerator>, + IEnumerator> +{ + public abstract PageResult GetPageFromResult(ClientResult result); + + public PageResult GetCurrentPage() + { + if (Current is null) + { + return GetPageFromResult(GetFirst()); + } + + return ((IEnumerator>)this).Current; + } + + public async Task> GetCurrentPageAsync() + { + if (Current is null) + { + return GetPageFromResult(await GetFirstAsync().ConfigureAwait(false)); + } + + return ((IEnumerator>)this).Current; + } + + PageResult IEnumerator>.Current + { + get + { + if (Current is null) + { + return default!; + } + + return GetPageFromResult(Current); + } + } + + PageResult IAsyncEnumerator>.Current + { + get + { + if (Current is null) + { + return default!; + } + + return GetPageFromResult(Current); + } + } +} diff --git a/src/To.Be.Generated/Internal/PageResultEnumerator.cs b/src/To.Be.Generated/Internal/PageResultEnumerator.cs new file mode 100644 index 000000000..89932e02a --- /dev/null +++ b/src/To.Be.Generated/Internal/PageResultEnumerator.cs @@ -0,0 +1,88 @@ +using System; +using System.ClientModel; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +// Note: implements both sync and async enumerator interfaces. +internal abstract class PageResultEnumerator : IAsyncEnumerator, IEnumerator +{ + private ClientResult? _current; + private bool _hasNext = true; + + public ClientResult Current => _current!; + + #region Service-specific methods that need to be generated on the subclient + + public abstract Task GetFirstAsync(); + + public abstract ClientResult GetFirst(); + + public abstract Task GetNextAsync(ClientResult result); + + public abstract ClientResult GetNext(ClientResult result); + + public abstract bool HasNext(ClientResult result); + + #endregion + + #region IEnumerator implementation + + object IEnumerator.Current => ((IEnumerator)this).Current; + + public bool MoveNext() + { + if (!_hasNext) + { + return false; + } + + if (_current == null) + { + _current = GetFirst(); + } + else + { + _current = GetNext(_current); + } + + _hasNext = HasNext(_current); + return true; + } + + void IEnumerator.Reset() => _current = null; + + void IDisposable.Dispose() { } + + #endregion + + #region IAsyncEnumerator implementation + + public async ValueTask MoveNextAsync() + { + if (!_hasNext) + { + return false; + } + + if (_current == null) + { + _current = await GetFirstAsync().ConfigureAwait(false); + } + else + { + _current = await GetNextAsync(_current).ConfigureAwait(false); + } + + _hasNext = HasNext(_current); + return true; + } + + ValueTask IAsyncDisposable.DisposeAsync() => default; + + #endregion +} \ No newline at end of file diff --git a/src/To.Be.Generated/MessagesPageEnumerator.cs b/src/To.Be.Generated/MessagesPageEnumerator.cs new file mode 100644 index 000000000..8ed555cd0 --- /dev/null +++ b/src/To.Be.Generated/MessagesPageEnumerator.cs @@ -0,0 +1,146 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +internal partial class MessagesPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _threadId; + private readonly int? _limit; + private readonly string _order; + + // Note: this one is special + private string _after; + + private readonly string _before; + private readonly RequestOptions _options; + + public MessagesPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + string threadId, + int? limit, string order, string after, string before, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _threadId = threadId; + _limit = limit; + _order = order; + _after = after; + _before = before; + + _options = options; + } + + public override async Task GetFirstAsync() + => await GetMessagesPageAsync(_threadId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetMessagesPage(_threadId, _limit, _order, _after, _before, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetMessagesPageAsync(_threadId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetMessagesPage(_threadId, _limit, _order, _after, _before, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListMessagesResponse list = ModelReaderWriter.Read(response.Content)!; + + MessagesPageToken pageToken = MessagesPageToken.FromOptions(_threadId, _limit, _order, _after, _before); + MessagesPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal virtual async Task GetMessagesPageAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + + using PipelineMessage message = CreateGetMessagesRequest(threadId, limit, order, after, before, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetMessagesPage(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + + using PipelineMessage message = CreateGetMessagesRequest(threadId, limit, order, after, before, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + private PipelineMessage CreateGetMessagesRequest(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/messages", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/MessagesPageToken.cs b/src/To.Be.Generated/MessagesPageToken.cs new file mode 100644 index 000000000..b11bca627 --- /dev/null +++ b/src/To.Be.Generated/MessagesPageToken.cs @@ -0,0 +1,158 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Assistants; + +internal class MessagesPageToken : ContinuationToken +{ + protected MessagesPageToken(string threadId, int? limit, string? order, string? after, string? before) + { + ThreadId = threadId; + + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public string ThreadId { get; } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + writer.WriteString("threadId", ThreadId); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public MessagesPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new(ThreadId, Limit, Order, lastId, Before); + } + + public static MessagesPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is MessagesPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create MessagesPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + string threadId = null!; + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "threadId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (threadId is null) + { + throw new ArgumentException("Failed to create MessagesPageToken from provided pageToken.", nameof(pageToken)); + } + + return new(threadId, limit, order, after, before); + } + + public static MessagesPageToken FromOptions(string threadId, int? limit, string? order, string? after, string? before) + => new(threadId, limit, order, after, before); +} \ No newline at end of file diff --git a/src/To.Be.Generated/RunStepsPageEnumerator.cs b/src/To.Be.Generated/RunStepsPageEnumerator.cs new file mode 100644 index 000000000..3cb0b285d --- /dev/null +++ b/src/To.Be.Generated/RunStepsPageEnumerator.cs @@ -0,0 +1,153 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +internal partial class RunStepsPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _threadId; + private readonly string _runId; + + private readonly int? _limit; + private readonly string? _order; + + // Note: this one is special + private string? _after; + + private readonly string? _before; + private readonly RequestOptions _options; + + public RunStepsPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + string threadId, string runId, + int? limit, string? order, string? after, string? before, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _threadId = threadId; + _runId = runId; + + _limit = limit; + _order = order; + _after = after; + _before = before; + _options = options; + } + + public override async Task GetFirstAsync() + => await GetRunStepsPageAsync(_threadId, _runId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetRunStepsPage(_threadId, _runId, _limit, _order, _after, _before, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetRunStepsPageAsync(_threadId, _runId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetRunStepsPage(_threadId, _runId, _limit, _order, _after, _before, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListRunStepsResponse list = ModelReaderWriter.Read(response.Content)!; + + RunStepsPageToken pageToken = RunStepsPageToken.FromOptions(_threadId, _runId, _limit, _order, _after, _before); + RunStepsPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal async virtual Task GetRunStepsPageAsync(string threadId, string runId, int? limit, string? order, string? after, string? before, RequestOptions? options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + + using PipelineMessage message = CreateGetRunStepsRequest(threadId, runId, limit, order, after, before, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetRunStepsPage(string threadId, string runId, int? limit, string? order, string? after, string? before, RequestOptions? options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + + using PipelineMessage message = CreateGetRunStepsRequest(threadId, runId, limit, order, after, before, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + private PipelineMessage CreateGetRunStepsRequest(string threadId, string runId, int? limit, string? order, string? after, string? before, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + uri.AppendPath("/steps", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/RunStepsPageToken.cs b/src/To.Be.Generated/RunStepsPageToken.cs new file mode 100644 index 000000000..229f6f16d --- /dev/null +++ b/src/To.Be.Generated/RunStepsPageToken.cs @@ -0,0 +1,168 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Assistants; + +internal class RunStepsPageToken : ContinuationToken +{ + protected RunStepsPageToken(string threadId, string runId, int? limit, string? order, string? after, string? before) + { + ThreadId = threadId; + RunId = runId; + + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public string ThreadId { get; } + + public string RunId { get; } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + writer.WriteString("threadId", ThreadId); + writer.WriteString("runId", ThreadId); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public RunStepsPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new RunStepsPageToken(ThreadId, RunId, Limit, Order, After, Before); + } + + public static RunStepsPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is RunStepsPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create RunStepsPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + string threadId = null!; + string runId = null!; + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "threadId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + case "runId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (threadId is null || runId is null) + { + throw new ArgumentException("Failed to create RunStepsPageToken from provided pageToken.", nameof(pageToken)); + } + + return new(threadId, runId, limit, order, after, before); + } + + public static RunStepsPageToken FromOptions(string threadId, string runId, int? limit, string? order, string? after, string? before) + => new RunStepsPageToken(threadId, runId, limit, order, after, before); +} \ No newline at end of file diff --git a/src/To.Be.Generated/RunsPageEnumerator.cs b/src/To.Be.Generated/RunsPageEnumerator.cs new file mode 100644 index 000000000..0f1e6dc89 --- /dev/null +++ b/src/To.Be.Generated/RunsPageEnumerator.cs @@ -0,0 +1,144 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +internal partial class RunsPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _threadId; + private readonly int? _limit; + private readonly string _order; + + // Note: this one is special + private string _after; + + private readonly string _before; + private readonly RequestOptions _options; + + public RunsPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + string threadId, int? limit, string order, string after, string before, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _threadId = threadId; + _limit = limit; + _order = order; + _after = after; + _before = before; + _options = options; + } + + public override async Task GetFirstAsync() + => await GetRunsPageAsync(_threadId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetRunsPage(_threadId, _limit, _order, _after, _before, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetRunsPageAsync(_threadId, _limit, _order, _after, _before, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetRunsPage(_threadId, _limit, _order, _after, _before, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListRunsResponse list = ModelReaderWriter.Read(response.Content)!; + + RunsPageToken pageToken = RunsPageToken.FromOptions(_threadId, _limit, _order, _after, _before); + RunsPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal async virtual Task GetRunsPageAsync(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + + using PipelineMessage message = CreateGetRunsRequest(threadId, limit, order, after, before, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetRunsPage(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + + using PipelineMessage message = CreateGetRunsRequest(threadId, limit, order, after, before, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + private PipelineMessage CreateGetRunsRequest(string threadId, int? limit, string order, string after, string before, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/RunsPageToken.cs b/src/To.Be.Generated/RunsPageToken.cs new file mode 100644 index 000000000..e019fe5d4 --- /dev/null +++ b/src/To.Be.Generated/RunsPageToken.cs @@ -0,0 +1,158 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Assistants; + +internal class RunsPageToken : ContinuationToken +{ + protected RunsPageToken(string threadId, int? limit, string? order, string? after, string? before) + { + ThreadId = threadId; + + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public string ThreadId { get; } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + writer.WriteString("threadId", ThreadId); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public RunsPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new RunsPageToken(ThreadId, Limit, Order, After, Before); + } + + public static RunsPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is RunsPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create RunsPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + string threadId = null!; + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "threadId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (threadId is null) + { + throw new ArgumentException("Failed to create RunsPageToken from provided pageToken.", nameof(pageToken)); + } + + return new(threadId, limit, order, after, before); + } + + public static RunsPageToken FromOptions(string threadId, int? limit, string? order, string? after, string? before) + => new RunsPageToken(threadId, limit, order, after, before); +} \ No newline at end of file diff --git a/src/To.Be.Generated/ThreadRunOperation.Protocol.cs b/src/To.Be.Generated/ThreadRunOperation.Protocol.cs new file mode 100644 index 000000000..224b07a85 --- /dev/null +++ b/src/To.Be.Generated/ThreadRunOperation.Protocol.cs @@ -0,0 +1,311 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +// Protocol version +public partial class ThreadRunOperation : OperationResult +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly ThreadRunPoller _poller; + + internal ThreadRunOperation( + ClientPipeline pipeline, + Uri endpoint, + string threadId, + string runId, + PipelineResponse response, + ThreadRunPoller poller) + : base(ThreadRunOperationToken.FromOptions(threadId, runId), response) + { + Argument.AssertNotNullOrEmpty(threadId, nameof(threadId)); + Argument.AssertNotNullOrEmpty(runId, nameof(runId)); + + _threadId = threadId; + _runId = runId; + + _pipeline = pipeline; + _endpoint = endpoint; + + _poller = poller; + } + + // TODO: add "wait for status change" overloads if needed. + + // TODO: take parameters? + public async Task WaitForCompletionResultAsync() + { + await _poller.WaitForCompletionAsync().ConfigureAwait(false); + HasCompleted = true; + return _poller.Current; + } + + public ClientResult WaitForCompletionResult() + { + _poller.WaitForCompletion(); + HasCompleted = true; + return _poller.Current; + } + + /// + /// [Protocol Method] Modifies a run. + /// + /// The content to send as the body of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task ModifyRunAsync(BinaryContent content, RequestOptions? options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateModifyRunRequest(_threadId, _runId, content, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Modifies a run. + /// + /// The content to send as the body of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult ModifyRun(BinaryContent content, RequestOptions? options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateModifyRunRequest(_threadId, _runId, content, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Cancels a run that is `in_progress`. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task CancelRunAsync(RequestOptions? options) + { + using PipelineMessage message = CreateCancelRunRequest(_threadId, _runId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Cancels a run that is `in_progress`. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult CancelRun(RequestOptions? options) + { + using PipelineMessage message = CreateCancelRunRequest(_threadId, _runId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] When a run has the `status: "requires_action"` and `required_action.type` is + /// `submit_tool_outputs`, this endpoint can be used to submit the outputs from the tool calls once + /// they're all completed. All outputs must be submitted in a single request. + /// + /// The content to send as the body of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task SubmitToolOutputsToRunAsync(BinaryContent content, RequestOptions? options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + PipelineMessage? message = null; + try + { + message = CreateSubmitToolOutputsToRunRequest(_threadId, _runId, content, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + finally + { + if (options?.BufferResponse != false) + { + message?.Dispose(); + } + } + } + + /// + /// [Protocol Method] When a run has the `status: "requires_action"` and `required_action.type` is + /// `submit_tool_outputs`, this endpoint can be used to submit the outputs from the tool calls once + /// they're all completed. All outputs must be submitted in a single request. + /// + /// The content to send as the body of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult SubmitToolOutputsToRun(BinaryContent content, RequestOptions? options = null) + { + Argument.AssertNotNull(content, nameof(content)); + + PipelineMessage? message = null; + try + { + message = CreateSubmitToolOutputsToRunRequest(_threadId, _runId, content, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + finally + { + if (options?.BufferResponse != false) + { + message?.Dispose(); + } + } + } + + public virtual IAsyncEnumerable GetRunStepsAsync(int? limit, string order, string after, string before, RequestOptions options) + { + PageResultEnumerator enumerator = new RunStepsPageEnumerator(_pipeline, _endpoint, _threadId, _runId, limit, order, after, before, options); + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual IEnumerable GetRunSteps(int? limit, string order, string after, string before, RequestOptions options) + { + PageResultEnumerator enumerator = new RunStepsPageEnumerator(_pipeline, _endpoint, _threadId, _runId, limit, order, after, before, options); + return PageCollectionHelpers.Create(enumerator); + } + + /// + /// [Protocol Method] Retrieves a run step. + /// + /// The ID of the run step to retrieve. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetRunStep(string stepId, RequestOptions? options) + { + Argument.AssertNotNullOrEmpty(stepId, nameof(stepId)); + + using PipelineMessage message = CreateGetRunStepRequest(_threadId, _runId, stepId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateModifyRunRequest(string threadId, string runId, BinaryContent content, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + request.Headers.Set("Content-Type", "application/json"); + request.Content = content; + message.Apply(options); + return message; + } + + internal PipelineMessage CreateCancelRunRequest(string threadId, string runId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + uri.AppendPath("/cancel", false); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateSubmitToolOutputsToRunRequest(string threadId, string runId, BinaryContent content, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "POST"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + uri.AppendPath("/submit_tool_outputs", false); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + request.Headers.Set("Content-Type", "application/json"); + request.Content = content; + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetRunStepsRequest(string threadId, string runId, int? limit, string order, string after, string before, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + uri.AppendPath("/steps", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + internal PipelineMessage CreateGetRunStepRequest(string threadId, string runId, string stepId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + uri.AppendPath("/steps/", false); + uri.AppendPath(stepId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} \ No newline at end of file diff --git a/src/To.Be.Generated/ThreadRunOperation.cs b/src/To.Be.Generated/ThreadRunOperation.cs new file mode 100644 index 000000000..27241b58f --- /dev/null +++ b/src/To.Be.Generated/ThreadRunOperation.cs @@ -0,0 +1,270 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.Assistants; + +// Convenience version +public partial class ThreadRunOperation : OperationResult +{ + private readonly string _threadId; + private readonly string _runId; + + // TODO: Do the IDs need to be public? + public string ThreadId => _threadId; + + public string RunId => _runId; + + public ThreadRun Value => _poller.Value; + + public RunStatus Status => _poller.Value.Status; + + public async Task WaitForCompletionAsync() + { + await _poller.WaitForCompletionAsync().ConfigureAwait(false); + + HasCompleted = true; + + return _poller.Value; + } + + public ThreadRun WaitForCompletion() + { + _poller.WaitForCompletion(); + HasCompleted = true; + return _poller.Value; + } + + // Question: what value is being computed here? + // Hypothesis: it's just the thread run value itself - which is progressively updated + // over the course of the thread run. + // Question: is this true for streaming too? What's the usage pattern here? + // For now, let's put a ThreadRun object on this that we'll update while polling, + // and loop back to see if that abstraction works across both polling and streaming + // LROs. + + // Note that since ThreadRun is a convenience model, we may need to illustrate + // protocol and convenience versions of this, and show that evolution. + + // TODO: Add state-machine specific WaitForStatusChange overloads. + + /// + /// Cancels an in-progress . + /// + /// A token that can be used to cancel this method call. + /// An updated instance, reflecting the new status of the run. + public virtual async Task> CancelRunAsync(CancellationToken cancellationToken = default) + { + ClientResult protocolResult = await CancelRunAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); + return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + } + + /// + /// Cancels an in-progress . + /// + /// A token that can be used to cancel this method call. + /// An updated instance, reflecting the new status of the run. + public virtual ClientResult CancelRun(CancellationToken cancellationToken = default) + { + ClientResult protocolResult = CancelRun(cancellationToken.ToRequestOptions()); + return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + } + + /// + /// Submits a collection of required tool call outputs to a run and resumes the run. + /// + /// + /// The tool outputs, corresponding to instances from the run. + /// + /// A token that can be used to cancel this method call. + /// The , updated after the submission was processed. + public virtual async Task> SubmitToolOutputsToRunAsync( + IEnumerable toolOutputs, + CancellationToken cancellationToken = default) + { + BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs).ToBinaryContent(); + ClientResult protocolResult = await SubmitToolOutputsToRunAsync(content, cancellationToken.ToRequestOptions()) + .ConfigureAwait(false); + return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + } + + /// + /// Submits a collection of required tool call outputs to a run and resumes the run. + /// + /// + /// The tool outputs, corresponding to instances from the run. + /// + /// A token that can be used to cancel this method call. + /// The , updated after the submission was processed. + public virtual ClientResult SubmitToolOutputsToRun( + IEnumerable toolOutputs, + CancellationToken cancellationToken = default) + { + BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs).ToBinaryContent(); + ClientResult protocolResult = SubmitToolOutputsToRun(content, cancellationToken.ToRequestOptions()); + return CreateResultFromProtocol(protocolResult, ThreadRun.FromResponse); + } + + /// + /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. + /// + /// + /// The tool outputs, corresponding to instances from the run. + /// + /// A token that can be used to cancel this method call. + public virtual AsyncCollectionResult SubmitToolOutputsToRunStreamingAsync( + IEnumerable toolOutputs, + CancellationToken cancellationToken = default) + { + BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs.ToList(), stream: true, null) + .ToBinaryContent(); + + async Task getResultAsync() => + await SubmitToolOutputsToRunAsync(content, cancellationToken.ToRequestOptions(streaming: true)) + .ConfigureAwait(false); + + return new AsyncStreamingUpdateCollection(getResultAsync); + } + + /// + /// Submits a collection of required tool call outputs to a run and resumes the run with streaming enabled. + /// + /// + /// The tool outputs, corresponding to instances from the run. + /// + /// A token that can be used to cancel this method call. + public virtual CollectionResult SubmitToolOutputsToRunStreaming( + IEnumerable toolOutputs, + CancellationToken cancellationToken = default) + { + BinaryContent content = new InternalSubmitToolOutputsRunRequest(toolOutputs.ToList(), stream: true, null) + .ToBinaryContent(); + + ClientResult getResult() => SubmitToolOutputsToRun(content, cancellationToken.ToRequestOptions(streaming: true)); + + return new StreamingUpdateCollection(getResult); + } + + /// + /// Gets a single run step from a run. + /// + /// The ID of the run step. + /// A token that can be used to cancel this method call. + /// A instance corresponding to the specified step. + public virtual async Task> GetRunStepAsync(string stepId, CancellationToken cancellationToken = default) + { + ClientResult protocolResult = await GetRunStepAsync(stepId, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + return CreateResultFromProtocol(protocolResult, RunStep.FromResponse); + } + + /// + /// Gets a single run step from a run. + /// + /// The ID of the run step. + /// A token that can be used to cancel this method call. + /// A instance corresponding to the specified step. + public virtual ClientResult GetRunStep(string stepId, CancellationToken cancellationToken = default) + { + ClientResult protocolResult = GetRunStep(stepId, cancellationToken.ToRequestOptions()); + return CreateResultFromProtocol(protocolResult, RunStep.FromResponse); + } + + /// + /// Gets a collection of instances associated with a . + /// + /// + /// A token that can be used to cancel this method call. + /// + public virtual AsyncPageCollection GetRunStepsAsync( + RunStepCollectionOptions? options = default, + CancellationToken cancellationToken = default) + { + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, + _threadId, + _runId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + public virtual AsyncPageCollection GetRunStepsAsync( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + RunStepsPageToken pageToken = RunStepsPageToken.FromToken(firstPageToken); + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.RunId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.CreateAsync(enumerator); + } + + /// + /// Gets a collection of instances associated with a . + /// + /// + /// A token that can be used to cancel this method call. + /// + public virtual PageCollection GetRunSteps( + RunStepCollectionOptions? options = default, + CancellationToken cancellationToken = default) + { + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, + _threadId, + _runId, + options?.PageSize, + options?.Order?.ToString(), + options?.AfterId, + options?.BeforeId, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + public virtual PageCollection GetRunSteps( + ContinuationToken firstPageToken, + CancellationToken cancellationToken = default) + { + Argument.AssertNotNull(firstPageToken, nameof(firstPageToken)); + + RunStepsPageToken pageToken = RunStepsPageToken.FromToken(firstPageToken); + RunStepsPageEnumerator enumerator = new(_pipeline, _endpoint, + pageToken.ThreadId, + pageToken.RunId, + pageToken.Limit, + pageToken.Order, + pageToken.After, + pageToken.Before, + cancellationToken.ToRequestOptions()); + + return PageCollectionHelpers.Create(enumerator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ClientResult CreateResultFromProtocol( + ClientResult protocolResult, + Func responseDeserializer) + { + PipelineResponse pipelineResponse = protocolResult.GetRawResponse(); + T deserializedResultValue = responseDeserializer.Invoke(pipelineResponse); + return ClientResult.FromValue(deserializedResultValue, pipelineResponse); + } +} diff --git a/src/To.Be.Generated/ThreadRunOperationToken.cs b/src/To.Be.Generated/ThreadRunOperationToken.cs new file mode 100644 index 000000000..8f9461d87 --- /dev/null +++ b/src/To.Be.Generated/ThreadRunOperationToken.cs @@ -0,0 +1,105 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.Assistants; + +internal class ThreadRunOperationToken : ContinuationToken +{ + private ThreadRunOperationToken(string threadId, string runId) + { + ThreadId = threadId; + RunId = runId; + } + + public string ThreadId { get; } + + public string RunId { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + + writer.WriteString("threadId", ThreadId); + writer.WriteString("runId", RunId); + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public static ThreadRunOperationToken FromToken(ContinuationToken continuationToken) + { + if (continuationToken is ThreadRunOperationToken token) + { + return token; + } + + BinaryData data = continuationToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create RunOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + Utf8JsonReader reader = new(data); + + string threadId = null!; + string runId = null!; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "threadId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + + case "runId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + threadId = reader.GetString()!; + break; + + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (threadId is null || runId is null) + { + throw new ArgumentException("Failed to create RunOperationToken from provided continuationToken.", nameof(continuationToken)); + } + + return new(threadId, runId); + } + + public static ThreadRunOperationToken FromOptions(string threadId, string runId) + => new ThreadRunOperationToken(threadId, runId); +} + diff --git a/src/To.Be.Generated/ThreadRunPoller.cs b/src/To.Be.Generated/ThreadRunPoller.cs new file mode 100644 index 000000000..d09f9c0b0 --- /dev/null +++ b/src/To.Be.Generated/ThreadRunPoller.cs @@ -0,0 +1,119 @@ +using OpenAI.Assistants; +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI; + +// This gets generated client-specific +// There is an evolution story around this from protocol to convenience +// When protocol-only, this inherits from OperationResultPoller +// When conveniences are added, this inherits from OperationPoller. +internal class ThreadRunPoller : OperationPoller +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + public readonly string _threadId; + public readonly string _runId; + + private readonly RequestOptions _options; + + internal ThreadRunPoller( + ClientPipeline pipeline, + Uri endpoint, + ClientResult result, + string threadId, + string runId, + RequestOptions options) : base(result) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _threadId = threadId; + _runId = runId; + + _options = options; + } + + public override ThreadRun GetValueFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + return ThreadRun.FromResponse(response); + } + + // Poller subclient method implementations + public override async Task UpdateStatusAsync() + => await GetRunAsync(_options).ConfigureAwait(false); + + public override ClientResult UpdateStatus() + => GetRun(_options); + + public override bool HasStopped(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + string status = doc.RootElement.GetProperty("status"u8).GetString()!; + + bool hasStopped = + status == "expired" || + status == "completed" || + status == "failed" || + status == "incomplete" || + status == "cancelled"; + + return hasStopped; + } + + // Protocol methods + + /// + /// [Protocol Method] Retrieves a run. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetRunAsync(RequestOptions? options) + { + using PipelineMessage message = CreateGetRunRequest(_threadId, _runId, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// + /// [Protocol Method] Retrieves a run. + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetRun(RequestOptions? options) + { + using PipelineMessage message = CreateGetRunRequest(_threadId, _runId, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateGetRunRequest(string threadId, string runId, RequestOptions? options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/threads/", false); + uri.AppendPath(threadId, true); + uri.AppendPath("/runs/", false); + uri.AppendPath(runId, true); + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/VectorStoreFileBatchesPageEnumerator.cs b/src/To.Be.Generated/VectorStoreFileBatchesPageEnumerator.cs new file mode 100644 index 000000000..a0b20f710 --- /dev/null +++ b/src/To.Be.Generated/VectorStoreFileBatchesPageEnumerator.cs @@ -0,0 +1,158 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal partial class VectorStoreFileBatchesPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _vectorStoreId; + private readonly string _batchId; + private readonly int? _limit; + private readonly string? _order; + + // Note: this one is special + private string? _after; + + private readonly string? _before; + private readonly string? _filter; + private readonly RequestOptions _options; + + public VectorStoreFileBatchesPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _vectorStoreId = vectorStoreId; + _batchId = batchId; + + _limit = limit; + _order = order; + _after = after; + _before = before; + _filter = filter; + + _options = options; + } + + public override async Task GetFirstAsync() + => await GetFileAssociationsAsync(_vectorStoreId, _batchId, _limit, _order, _after, _before, _filter, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetFileAssociations(_vectorStoreId, _batchId, _limit, _order, _after, _before, _filter, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetFileAssociationsAsync(_vectorStoreId, _batchId, _limit, _order, _after, _before, _filter, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetFileAssociations(_vectorStoreId, _batchId, _limit, _order, _after, _before, _filter, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListVectorStoreFilesResponse list = ModelReaderWriter.Read(response.Content)!; + + VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromOptions(_vectorStoreId, _limit, _order, _after, _before, _filter); + VectorStoreFilesPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal virtual async Task GetFileAssociationsAsync(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); + + using PipelineMessage message = CreateGetFilesInVectorStoreBatchesRequest(vectorStoreId, batchId, limit, order, after, before, filter, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetFileAssociations(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + Argument.AssertNotNullOrEmpty(batchId, nameof(batchId)); + + using PipelineMessage message = CreateGetFilesInVectorStoreBatchesRequest(vectorStoreId, batchId, limit, order, after, before, filter, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateGetFilesInVectorStoreBatchesRequest(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/file_batches/", false); + uri.AppendPath(batchId, true); + uri.AppendPath("/files", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + if (filter != null) + { + uri.AppendQuery("filter", filter, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/VectorStoreFileBatchesPageToken.cs b/src/To.Be.Generated/VectorStoreFileBatchesPageToken.cs new file mode 100644 index 000000000..798f0c760 --- /dev/null +++ b/src/To.Be.Generated/VectorStoreFileBatchesPageToken.cs @@ -0,0 +1,183 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class VectorStoreFileBatchesPageToken : ContinuationToken +{ + protected VectorStoreFileBatchesPageToken(string vectorStoreId,string batchId, int? limit, string? order, string? after, string? before, string? filter) + { + VectorStoreId = vectorStoreId; + BatchId = batchId; + + Limit = limit; + Order = order; + After = after; + Before = before; + Filter = filter; + } + + public string VectorStoreId { get; } + + public string BatchId { get; } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public string? Filter { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + writer.WriteString("vectorStoreId", VectorStoreId); + writer.WriteString("batchId", BatchId); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + if (Filter is not null) + { + writer.WriteString("filter", Filter); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public VectorStoreFileBatchesPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new(VectorStoreId, BatchId, Limit, Order, lastId, Before, Filter); + } + + public static VectorStoreFileBatchesPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is VectorStoreFileBatchesPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create VectorStoreFileBatchesPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + string vectorStoreId = null!; + string batchId = null!; + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + string? filter = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "vectorStoreId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + case "batchId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + case "filter": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + filter = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (vectorStoreId is null || + batchId is null) + { + throw new ArgumentException("Failed to create VectorStoreFileBatchesPageToken from provided pageToken.", nameof(pageToken)); + } + + return new(vectorStoreId, batchId, limit, order, after, before, filter); + } + + public static VectorStoreFileBatchesPageToken FromOptions(string vectorStoreId, string batchId, int? limit, string? order, string? after, string? before, string? filter) + => new(vectorStoreId, batchId, limit, order, after, before, filter); +} \ No newline at end of file diff --git a/src/To.Be.Generated/VectorStoreFilesPageEnumerator.cs b/src/To.Be.Generated/VectorStoreFilesPageEnumerator.cs new file mode 100644 index 000000000..d4825a0e9 --- /dev/null +++ b/src/To.Be.Generated/VectorStoreFilesPageEnumerator.cs @@ -0,0 +1,151 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal partial class VectorStoreFilesPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly string _vectorStoreId; + private readonly int? _limit; + private readonly string? _order; + + // Note: this one is special + private string? _after; + + private readonly string? _before; + private readonly string? _filter; + private readonly RequestOptions _options; + + public VectorStoreFilesPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + string vectorStoreId, + int? limit, string? order, string? after, string? before, string? filter, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _vectorStoreId = vectorStoreId; + _limit = limit; + _order = order; + _after = after; + _before = before; + _filter = filter; + _options = options; + } + + public override async Task GetFirstAsync() + => await GetFileAssociationsAsync(_vectorStoreId, _limit, _order, _after, _before, _filter, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetFileAssociations(_vectorStoreId, _limit, _order, _after, _before, _filter, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetFileAssociationsAsync(_vectorStoreId, _limit, _order, _after, _before, _filter, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetFileAssociations(_vectorStoreId, _limit, _order, _after, _before, _filter, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListVectorStoreFilesResponse list = ModelReaderWriter.Read(response.Content)!; + + VectorStoreFilesPageToken pageToken = VectorStoreFilesPageToken.FromOptions(_vectorStoreId, _limit, _order, _after, _before, _filter); + VectorStoreFilesPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal virtual async Task GetFileAssociationsAsync(string vectorStoreId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + + using PipelineMessage message = CreateGetVectorStoreFilesRequest(vectorStoreId, limit, order, after, before, filter, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetFileAssociations(string vectorStoreId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(vectorStoreId, nameof(vectorStoreId)); + + using PipelineMessage message = CreateGetVectorStoreFilesRequest(vectorStoreId, limit, order, after, before, filter, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + internal PipelineMessage CreateGetVectorStoreFilesRequest(string vectorStoreId, int? limit, string? order, string? after, string? before, string? filter, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores/", false); + uri.AppendPath(vectorStoreId, true); + uri.AppendPath("/files", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + if (filter != null) + { + uri.AppendQuery("filter", filter, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/VectorStoreFilesPageToken.cs b/src/To.Be.Generated/VectorStoreFilesPageToken.cs new file mode 100644 index 000000000..ba7b1f9eb --- /dev/null +++ b/src/To.Be.Generated/VectorStoreFilesPageToken.cs @@ -0,0 +1,171 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class VectorStoreFilesPageToken : ContinuationToken +{ + protected VectorStoreFilesPageToken(string vectorStoreId, int? limit, string? order, string? after, string? before, string? filter) + { + VectorStoreId = vectorStoreId; + + Limit = limit; + Order = order; + After = after; + Before = before; + Filter = filter; + } + public string VectorStoreId { get; } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public string? Filter { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + writer.WriteString("vectorStoreId", VectorStoreId); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + if (Filter is not null) + { + writer.WriteString("filter", Filter); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public VectorStoreFilesPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new(VectorStoreId, Limit, Order, lastId, Before, Filter); + } + + public static VectorStoreFilesPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is VectorStoreFilesPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create VectorStoreFilesPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + string vectorStoreId = null!; + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + string? filter = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "vectorStoreId": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + vectorStoreId = reader.GetString()!; + break; + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + case "filter": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + filter = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + if (vectorStoreId is null) + { + throw new ArgumentException("Failed to create VectorStoreFilesPageToken from provided pageToken.", nameof(pageToken)); + } + + return new(vectorStoreId, limit, order, after, before, filter); + } + + public static VectorStoreFilesPageToken FromOptions(string vectorStoreId, int? limit, string? order, string? after, string? before, string? filter) + => new(vectorStoreId, limit, order, after, before, filter); +} \ No newline at end of file diff --git a/src/To.Be.Generated/VectorStoresPageEnumerator.cs b/src/To.Be.Generated/VectorStoresPageEnumerator.cs new file mode 100644 index 000000000..94ac83e5d --- /dev/null +++ b/src/To.Be.Generated/VectorStoresPageEnumerator.cs @@ -0,0 +1,136 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; +using System.Threading.Tasks; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal partial class VectorStoresPageEnumerator : PageEnumerator +{ + private readonly ClientPipeline _pipeline; + private readonly Uri _endpoint; + + private readonly int? _limit; + private readonly string _order; + + // Note: this one is special + private string _after; + + private readonly string _before; + private readonly RequestOptions _options; + + public VectorStoresPageEnumerator( + ClientPipeline pipeline, + Uri endpoint, + int? limit, string order, string after, string before, + RequestOptions options) + { + _pipeline = pipeline; + _endpoint = endpoint; + + _limit = limit; + _order = order; + _after = after; + _before = before; + _options = options; + } + + public override async Task GetFirstAsync() + => await GetVectorStoresAsync(_limit, _order, _after, _before, _options).ConfigureAwait(false); + + public override ClientResult GetFirst() + => GetVectorStores(_limit, _order, _after, _before, _options); + + public override async Task GetNextAsync(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return await GetVectorStoresAsync(_limit, _order, _after, _before, _options).ConfigureAwait(false); + } + + public override ClientResult GetNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + _after = doc.RootElement.GetProperty("last_id"u8).GetString()!; + + return GetVectorStores(_limit, _order, _after, _before, _options); + } + + public override bool HasNext(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + using JsonDocument doc = JsonDocument.Parse(response.Content); + bool hasMore = doc.RootElement.GetProperty("has_more"u8).GetBoolean(); + + return hasMore; + } + + // Note: this is the deserialization method that converts protocol to convenience + public override PageResult GetPageFromResult(ClientResult result) + { + PipelineResponse response = result.GetRawResponse(); + + InternalListVectorStoresResponse list = ModelReaderWriter.Read(response.Content)!; + + VectorStoresPageToken pageToken = VectorStoresPageToken.FromOptions(_limit, _order, _after, _before); + VectorStoresPageToken? nextPageToken = pageToken.GetNextPageToken(list.HasMore, list.LastId); + + return PageResult.Create(list.Data, pageToken, nextPageToken, response); + } + + // Note: these are the protocol methods - they are generated here + internal virtual async Task GetVectorStoresAsync(int? limit, string order, string after, string before, RequestOptions options) + { + using PipelineMessage message = CreateGetVectorStoresRequest(limit, order, after, before, options); + return ClientResult.FromResponse(await _pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + internal virtual ClientResult GetVectorStores(int? limit, string order, string after, string before, RequestOptions options) + { + using PipelineMessage message = CreateGetVectorStoresRequest(limit, order, after, before, options); + return ClientResult.FromResponse(_pipeline.ProcessMessage(message, options)); + } + + private PipelineMessage CreateGetVectorStoresRequest(int? limit, string order, string after, string before, RequestOptions options) + { + var message = _pipeline.CreateMessage(); + message.ResponseClassifier = PipelineMessageClassifier200; + var request = message.Request; + request.Method = "GET"; + var uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/vector_stores", false); + if (limit != null) + { + uri.AppendQuery("limit", limit.Value, true); + } + if (order != null) + { + uri.AppendQuery("order", order, true); + } + if (after != null) + { + uri.AppendQuery("after", after, true); + } + if (before != null) + { + uri.AppendQuery("before", before, true); + } + request.Uri = uri.ToUri(); + request.Headers.Set("Accept", "application/json"); + message.Apply(options); + return message; + } + + private static PipelineMessageClassifier? _pipelineMessageClassifier200; + private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); +} diff --git a/src/To.Be.Generated/VectorStoresPageToken.cs b/src/To.Be.Generated/VectorStoresPageToken.cs new file mode 100644 index 000000000..bd4c29a2d --- /dev/null +++ b/src/To.Be.Generated/VectorStoresPageToken.cs @@ -0,0 +1,142 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +#nullable enable + +namespace OpenAI.VectorStores; + +internal class VectorStoresPageToken : ContinuationToken +{ + protected VectorStoresPageToken(int? limit, string? order, string? after, string? before) + { + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public int? Limit { get; } + + public string? Order { get; } + + public string? After { get; } + + public string? Before { get; } + + public override BinaryData ToBytes() + { + using MemoryStream stream = new(); + using Utf8JsonWriter writer = new(stream); + + writer.WriteStartObject(); + + if (Limit.HasValue) + { + writer.WriteNumber("limit", Limit.Value); + } + + if (Order is not null) + { + writer.WriteString("order", Order); + } + + if (After is not null) + { + writer.WriteString("after", After); + } + + if (Before is not null) + { + writer.WriteString("before", Before); + } + + writer.WriteEndObject(); + + writer.Flush(); + stream.Position = 0; + + return BinaryData.FromStream(stream); + } + + public VectorStoresPageToken? GetNextPageToken(bool hasMore, string? lastId) + { + if (!hasMore || lastId is null) + { + return null; + } + + return new(Limit, Order, lastId, Before); + } + + public static VectorStoresPageToken FromToken(ContinuationToken pageToken) + { + if (pageToken is VectorStoresPageToken token) + { + return token; + } + + BinaryData data = pageToken.ToBytes(); + + if (data.ToMemory().Length == 0) + { + throw new ArgumentException("Failed to create VectorStoresPageToken from provided pageToken.", nameof(pageToken)); + } + + Utf8JsonReader reader = new(data); + + int? limit = null; + string? order = null; + string? after = null; + string? before = null; + + reader.Read(); + + Debug.Assert(reader.TokenType == JsonTokenType.StartObject); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + + string propertyName = reader.GetString()!; + + switch (propertyName) + { + case "limit": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.Number); + limit = reader.GetInt32(); + break; + case "order": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + order = reader.GetString(); + break; + case "after": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + after = reader.GetString(); + break; + case "before": + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.String); + before = reader.GetString(); + break; + default: + throw new JsonException($"Unrecognized property '{propertyName}'."); + } + } + + return new(limit, order, after, before); + } + + public static VectorStoresPageToken FromOptions(int? limit, string? order, string? after, string? before) + => new(limit, order, after, before); +} \ No newline at end of file diff --git a/tests/Assistants/AssistantTests.cs b/tests/Assistants/AssistantTests.cs index 8ee92f319..a5bbc1996 100644 --- a/tests/Assistants/AssistantTests.cs +++ b/tests/Assistants/AssistantTests.cs @@ -6,7 +6,6 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -51,7 +50,8 @@ public void BasicAssistantOperationsWork() }, }); Assert.That(modifiedAssistant.Id, Is.EqualTo(assistant.Id)); - PageableCollection recentAssistants = client.GetAssistants(); + PageCollection pages = client.GetAssistants(); + IEnumerable recentAssistants = pages.GetAllValues(); Assistant listedAssistant = recentAssistants.FirstOrDefault(pageItem => pageItem.Id == assistant.Id); Assert.That(listedAssistant, Is.Not.Null); Assert.That(listedAssistant.Metadata.TryGetValue(s_cleanupMetadataKey, out string newMetadataValue) && newMetadataValue == "goodbye!"); @@ -128,10 +128,10 @@ public void BasicMessageOperationsWork() }); Assert.That(message.Metadata.TryGetValue("messageMetadata", out metadataValue) && metadataValue == "newValue"); - PageableCollection messagePage = client.GetMessages(thread); - Assert.That(messagePage.Count, Is.EqualTo(1)); - Assert.That(messagePage.First().Id, Is.EqualTo(message.Id)); - Assert.That(messagePage.First().Metadata.TryGetValue("messageMetadata", out metadataValue) && metadataValue == "newValue"); + PageResult messagePage = client.GetMessages(thread).GetCurrentPage(); + Assert.That(messagePage.Values.Count, Is.EqualTo(1)); + Assert.That(messagePage.Values[0].Id, Is.EqualTo(message.Id)); + Assert.That(messagePage.Values[0].Metadata.TryGetValue("messageMetadata", out metadataValue) && metadataValue == "newValue"); } [Test] @@ -159,16 +159,16 @@ public void ThreadWithInitialMessagesWorks() }; AssistantThread thread = client.CreateThread(options); Validate(thread); - PageableCollection messages = client.GetMessages(thread, resultOrder: ListOrder.OldestFirst); - Assert.That(messages.Count, Is.EqualTo(2)); - Assert.That(messages.First().Role, Is.EqualTo(MessageRole.User)); - Assert.That(messages.First().Content?.Count, Is.EqualTo(1)); - Assert.That(messages.First().Content[0].Text, Is.EqualTo("Hello, world!")); - Assert.That(messages.ElementAt(1).Content?.Count, Is.EqualTo(2)); - Assert.That(messages.ElementAt(1).Content[0], Is.Not.Null); - Assert.That(messages.ElementAt(1).Content[0].Text, Is.EqualTo("Can you describe this image for me?")); - Assert.That(messages.ElementAt(1).Content[1], Is.Not.Null); - Assert.That(messages.ElementAt(1).Content[1].ImageUrl.AbsoluteUri, Is.EqualTo("https://test.openai.com/image.png")); + PageResult messagesPage = client.GetMessages(thread, new MessageCollectionOptions() { Order = ListOrder.OldestFirst }).GetCurrentPage(); + Assert.That(messagesPage.Values.Count, Is.EqualTo(2)); + Assert.That(messagesPage.Values[0].Role, Is.EqualTo(MessageRole.User)); + Assert.That(messagesPage.Values[0].Content?.Count, Is.EqualTo(1)); + Assert.That(messagesPage.Values[0].Content[0].Text, Is.EqualTo("Hello, world!")); + Assert.That(messagesPage.Values[1].Content?.Count, Is.EqualTo(2)); + Assert.That(messagesPage.Values[1].Content[0], Is.Not.Null); + Assert.That(messagesPage.Values[1].Content[0].Text, Is.EqualTo("Can you describe this image for me?")); + Assert.That(messagesPage.Values[1].Content[1], Is.Not.Null); + Assert.That(messagesPage.Values[1].Content[1].ImageUrl.AbsoluteUri, Is.EqualTo("https://test.openai.com/image.png")); } [Test] @@ -179,27 +179,25 @@ public void BasicRunOperationsWork() Validate(assistant); AssistantThread thread = client.CreateThread(); Validate(thread); - PageableCollection runs = client.GetRuns(thread); - Assert.That(runs.Count, Is.EqualTo(0)); + PageResult runsPage = client.GetRuns(thread).GetCurrentPage(); + Assert.That(runsPage.Values.Count, Is.EqualTo(0)); ThreadMessage message = client.CreateMessage(thread.Id, MessageRole.User, ["Hello, assistant!"]); Validate(message); - ThreadRun run = client.CreateRun(thread.Id, assistant.Id); - Validate(run); - Assert.That(run.Status, Is.EqualTo(RunStatus.Queued)); - Assert.That(run.CreatedAt, Is.GreaterThan(s_2024)); - ThreadRun retrievedRun = client.GetRun(thread.Id, run.Id); - Assert.That(retrievedRun.Id, Is.EqualTo(run.Id)); - runs = client.GetRuns(thread); - Assert.That(runs.Count, Is.EqualTo(1)); - Assert.That(runs.First().Id, Is.EqualTo(run.Id)); - - PageableCollection messages = client.GetMessages(thread); - Assert.That(messages.Count, Is.GreaterThanOrEqualTo(1)); - for (int i = 0; i < 10 && !run.Status.IsTerminal; i++) - { - Thread.Sleep(500); - run = client.GetRun(run); - } + ThreadRunOperation runOperation = client.CreateRun(ReturnWhen.Started, thread.Id, assistant.Id); + Validate(runOperation); + Assert.That(runOperation.Status, Is.EqualTo(RunStatus.Queued)); + Assert.That(runOperation.Value.CreatedAt, Is.GreaterThan(s_2024)); + //ThreadRun retrievedRun = client.GetRun(thread.Id, run.Id); + //Assert.That(retrievedRun.Id, Is.EqualTo(run.Id)); + runsPage = client.GetRuns(thread).GetCurrentPage(); + Assert.That(runsPage.Values.Count, Is.EqualTo(1)); + Assert.That(runsPage.Values[0].Id, Is.EqualTo(runOperation.RunId)); + + PageResult messagesPage = client.GetMessages(thread).GetCurrentPage(); + Assert.That(messagesPage.Values.Count, Is.GreaterThanOrEqualTo(1)); + + ThreadRun run = runOperation.WaitForCompletion(); + Assert.That(run.Status, Is.EqualTo(RunStatus.Completed)); Assert.That(run.CompletedAt, Is.GreaterThan(s_2024)); Assert.That(run.RequiredActions.Count, Is.EqualTo(0)); @@ -207,12 +205,12 @@ public void BasicRunOperationsWork() Assert.That(run.FailedAt, Is.Null); Assert.That(run.IncompleteDetails, Is.Null); - messages = client.GetMessages(thread); - Assert.That(messages.Count, Is.EqualTo(2)); + messagesPage = client.GetMessages(thread).GetCurrentPage(); + Assert.That(messagesPage.Values.Count, Is.EqualTo(2)); - Assert.That(messages.ElementAt(0).Role, Is.EqualTo(MessageRole.Assistant)); - Assert.That(messages.ElementAt(1).Role, Is.EqualTo(MessageRole.User)); - Assert.That(messages.ElementAt(1).Id, Is.EqualTo(message.Id)); + Assert.That(messagesPage.Values[0].Role, Is.EqualTo(MessageRole.Assistant)); + Assert.That(messagesPage.Values[1].Role, Is.EqualTo(MessageRole.User)); + Assert.That(messagesPage.Values[1].Id, Is.EqualTo(message.Id)); } [Test] @@ -256,31 +254,29 @@ public void BasicRunStepFunctionalityWorks() }); Validate(thread); - ThreadRun run = client.CreateRun(thread, assistant); - Validate(run); + ThreadRunOperation runOperation = client.CreateRun(ReturnWhen.Completed, thread, assistant); + Validate(runOperation); - while (!run.Status.IsTerminal) - { - Thread.Sleep(1000); - run = client.GetRun(run); - } - Assert.That(run.Status, Is.EqualTo(RunStatus.Completed)); - Assert.That(run.Usage?.TotalTokens, Is.GreaterThan(0)); + Assert.That(runOperation.Status, Is.EqualTo(RunStatus.Completed)); + Assert.That(runOperation.Value.Usage?.TotalTokens, Is.GreaterThan(0)); + + PageCollection pages = runOperation.GetRunSteps(); + PageResult firstPage = pages.GetCurrentPage(); - PageableCollection runSteps = client.GetRunSteps(run); + IEnumerable runSteps = pages.GetAllValues(); Assert.That(runSteps.Count, Is.GreaterThan(1)); Assert.Multiple(() => { Assert.That(runSteps.First().AssistantId, Is.EqualTo(assistant.Id)); Assert.That(runSteps.First().ThreadId, Is.EqualTo(thread.Id)); - Assert.That(runSteps.First().RunId, Is.EqualTo(run.Id)); + Assert.That(runSteps.First().RunId, Is.EqualTo(runOperation.RunId)); Assert.That(runSteps.First().CreatedAt, Is.GreaterThan(s_2024)); Assert.That(runSteps.First().CompletedAt, Is.GreaterThan(s_2024)); }); RunStepDetails details = runSteps.First().Details; Assert.That(details?.CreatedMessageId, Is.Not.Null.And.Not.Empty); - string rawContent = runSteps.GetRawResponse().Content.ToString(); + string rawContent = firstPage.GetRawResponse().Content.ToString(); details = runSteps.ElementAt(1).Details; Assert.Multiple(() => { @@ -312,12 +308,12 @@ public void SettingResponseFormatWorks() Validate(thread); ThreadMessage message = client.CreateMessage(thread, MessageRole.User, ["Write some JSON for me!"]); Validate(message); - ThreadRun run = client.CreateRun(thread, assistant, new() + ThreadRunOperation runOperation = client.CreateRun(ReturnWhen.Started, thread, assistant, new() { ResponseFormat = AssistantResponseFormat.JsonObject, }); - Validate(run); - Assert.That(run.ResponseFormat, Is.EqualTo(AssistantResponseFormat.JsonObject)); + Validate(runOperation); + Assert.That(runOperation.Value.ResponseFormat, Is.EqualTo(AssistantResponseFormat.JsonObject)); } [Test] @@ -354,7 +350,8 @@ public void FunctionToolsWork() Assert.That(responseToolDefinition?.FunctionName, Is.EqualTo("get_favorite_food_for_day_of_week")); Assert.That(responseToolDefinition?.Parameters, Is.Not.Null); - ThreadRun run = client.CreateThreadAndRun( + ThreadRunOperation runOperation = client.CreateThreadAndRun( + ReturnWhen.Completed, assistant, new ThreadCreationOptions() { @@ -364,141 +361,133 @@ public void FunctionToolsWork() { AdditionalInstructions = "Call provided tools when appropriate.", }); - Validate(run); + Validate(runOperation); - for (int i = 0; i < 10 && !run.Status.IsTerminal; i++) - { - Thread.Sleep(500); - run = client.GetRun(run); - } - Assert.That(run.Status, Is.EqualTo(RunStatus.RequiresAction)); - Assert.That(run.RequiredActions?.Count, Is.EqualTo(1)); - Assert.That(run.RequiredActions[0].ToolCallId, Is.Not.Null.And.Not.Empty); - Assert.That(run.RequiredActions[0].FunctionName, Is.EqualTo("get_favorite_food_for_day_of_week")); - Assert.That(run.RequiredActions[0].FunctionArguments, Is.Not.Null.And.Not.Empty); + Assert.That(runOperation.Status, Is.EqualTo(RunStatus.RequiresAction)); + Assert.That(runOperation.Value.RequiredActions?.Count, Is.EqualTo(1)); + Assert.That(runOperation.Value.RequiredActions[0].ToolCallId, Is.Not.Null.And.Not.Empty); + Assert.That(runOperation.Value.RequiredActions[0].FunctionName, Is.EqualTo("get_favorite_food_for_day_of_week")); + Assert.That(runOperation.Value.RequiredActions[0].FunctionArguments, Is.Not.Null.And.Not.Empty); - run = client.SubmitToolOutputsToRun(run, [new(run.RequiredActions[0].ToolCallId, "tacos")]); - Assert.That(run.Status.IsTerminal, Is.False); + // TODO: Make this sample nice per APIs. + runOperation.SubmitToolOutputsToRun([new(runOperation.Value.RequiredActions[0].ToolCallId, "tacos")]); + Assert.That(runOperation.Status.IsTerminal, Is.False); - for (int i = 0; i < 10 && !run.Status.IsTerminal; i++) - { - Thread.Sleep(500); - run = client.GetRun(run); - } - Assert.That(run.Status, Is.EqualTo(RunStatus.Completed)); + runOperation.WaitForCompletion(); + Assert.That(runOperation.Status, Is.EqualTo(RunStatus.Completed)); - PageableCollection messages = client.GetMessages(run.ThreadId, resultOrder: ListOrder.NewestFirst); + IEnumerable messages = client.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.NewestFirst }).GetAllValues(); Assert.That(messages.Count, Is.GreaterThan(1)); Assert.That(messages.First().Role, Is.EqualTo(MessageRole.Assistant)); Assert.That(messages.First().Content?[0], Is.Not.Null); Assert.That(messages.First().Content[0].Text.ToLowerInvariant(), Does.Contain("tacos")); } - [Test] - public async Task StreamingRunWorks() - { - AssistantClient client = new(); - Assistant assistant = await client.CreateAssistantAsync("gpt-3.5-turbo"); - Validate(assistant); - - AssistantThread thread = await client.CreateThreadAsync(new ThreadCreationOptions() - { - InitialMessages = { "Hello there, assistant! How are you today?", }, - }); - Validate(thread); - - Stopwatch stopwatch = Stopwatch.StartNew(); - void Print(string message) => Console.WriteLine($"[{stopwatch.ElapsedMilliseconds,6}] {message}"); - - AsyncResultCollection streamingResult - = client.CreateRunStreamingAsync(thread.Id, assistant.Id); - - Print(">>> Connected <<<"); - - await foreach (StreamingUpdate update in streamingResult) - { - string message = $"{update.UpdateKind} "; - if (update is RunUpdate runUpdate) - { - message += $"at {update.UpdateKind switch - { - StreamingUpdateReason.RunCreated => runUpdate.Value.CreatedAt, - StreamingUpdateReason.RunQueued => runUpdate.Value.StartedAt, - StreamingUpdateReason.RunInProgress => runUpdate.Value.StartedAt, - StreamingUpdateReason.RunCompleted => runUpdate.Value.CompletedAt, - _ => "???", - }}"; - } - if (update is MessageContentUpdate contentUpdate) - { - if (contentUpdate.Role.HasValue) - { - message += $"[{contentUpdate.Role}]"; - } - message += $"[{contentUpdate.MessageIndex}] {contentUpdate.Text}"; - } - Print(message); - } - Print(">>> Done <<<"); - } - - [TestCase] - public async Task StreamingToolCall() - { - AssistantClient client = GetTestClient(); - FunctionToolDefinition getWeatherTool = new("get_current_weather", "Gets the user's current weather"); - Assistant assistant = await client.CreateAssistantAsync("gpt-3.5-turbo", new() - { - Tools = { getWeatherTool } - }); - Validate(assistant); - - Stopwatch stopwatch = Stopwatch.StartNew(); - void Print(string message) => Console.WriteLine($"[{stopwatch.ElapsedMilliseconds,6}] {message}"); - - Print(" >>> Beginning call ... "); - AsyncResultCollection asyncResults = client.CreateThreadAndRunStreamingAsync( - assistant, - new() - { - InitialMessages = { "What should I wear outside right now?", }, - }); - Print(" >>> Starting enumeration ..."); - - ThreadRun run = null; - - do - { - run = null; - List toolOutputs = []; - await foreach (StreamingUpdate update in asyncResults) - { - string message = update.UpdateKind.ToString(); - - if (update is RunUpdate runUpdate) - { - message += $" run_id:{runUpdate.Value.Id}"; - run = runUpdate.Value; - } - if (update is RequiredActionUpdate requiredActionUpdate) - { - Assert.That(requiredActionUpdate.FunctionName, Is.EqualTo(getWeatherTool.FunctionName)); - Assert.That(requiredActionUpdate.GetThreadRun().Status, Is.EqualTo(RunStatus.RequiresAction)); - message += $" {requiredActionUpdate.FunctionName}"; - toolOutputs.Add(new(requiredActionUpdate.ToolCallId, "warm and sunny")); - } - if (update is MessageContentUpdate contentUpdate) - { - message += $" {contentUpdate.Text}"; - } - Print(message); - } - if (toolOutputs.Count > 0) - { - asyncResults = client.SubmitToolOutputsToRunStreamingAsync(run, toolOutputs); - } - } while (run?.Status.IsTerminal == false); - } + //[Test] + //public async Task StreamingRunWorks() + //{ + // AssistantClient client = new(); + // Assistant assistant = await client.CreateAssistantAsync("gpt-3.5-turbo"); + // Validate(assistant); + + // AssistantThread thread = await client.CreateThreadAsync(new ThreadCreationOptions() + // { + // InitialMessages = { "Hello there, assistant! How are you today?", }, + // }); + // Validate(thread); + + // Stopwatch stopwatch = Stopwatch.StartNew(); + // void Print(string message) => Console.WriteLine($"[{stopwatch.ElapsedMilliseconds,6}] {message}"); + + // AsyncCollectionResult streamingResult + // = client.CreateRunStreamingAsync(thread.Id, assistant.Id); + + // Print(">>> Connected <<<"); + + // await foreach (StreamingUpdate update in streamingResult) + // { + // string message = $"{update.UpdateKind} "; + // if (update is RunUpdate runUpdate) + // { + // message += $"at {update.UpdateKind switch + // { + // StreamingUpdateReason.RunCreated => runUpdate.Value.CreatedAt, + // StreamingUpdateReason.RunQueued => runUpdate.Value.StartedAt, + // StreamingUpdateReason.RunInProgress => runUpdate.Value.StartedAt, + // StreamingUpdateReason.RunCompleted => runUpdate.Value.CompletedAt, + // _ => "???", + // }}"; + // } + // if (update is MessageContentUpdate contentUpdate) + // { + // if (contentUpdate.Role.HasValue) + // { + // message += $"[{contentUpdate.Role}]"; + // } + // message += $"[{contentUpdate.MessageIndex}] {contentUpdate.Text}"; + // } + // Print(message); + // } + // Print(">>> Done <<<"); + //} + + //[TestCase] + //public async Task StreamingToolCall() + //{ + // AssistantClient client = GetTestClient(); + // FunctionToolDefinition getWeatherTool = new("get_current_weather", "Gets the user's current weather"); + // Assistant assistant = await client.CreateAssistantAsync("gpt-3.5-turbo", new() + // { + // Tools = { getWeatherTool } + // }); + // Validate(assistant); + + // Stopwatch stopwatch = Stopwatch.StartNew(); + // void Print(string message) => Console.WriteLine($"[{stopwatch.ElapsedMilliseconds,6}] {message}"); + + // Print(" >>> Beginning call ... "); + // AsyncCollectionResult asyncResults = client.CreateThreadAndRunStreamingAsync( + // assistant, + // new() + // { + // InitialMessages = { "What should I wear outside right now?", }, + // }); + // Print(" >>> Starting enumeration ..."); + + // ThreadRun run = null; + + // do + // { + // run = null; + // List toolOutputs = []; + // await foreach (StreamingUpdate update in asyncResults) + // { + // string message = update.UpdateKind.ToString(); + + // if (update is RunUpdate runUpdate) + // { + // message += $" run_id:{runUpdate.Value.Id}"; + // run = runUpdate.Value; + // } + // if (update is RequiredActionUpdate requiredActionUpdate) + // { + // Assert.That(requiredActionUpdate.FunctionName, Is.EqualTo(getWeatherTool.FunctionName)); + // Assert.That(requiredActionUpdate.GetThreadRun().Status, Is.EqualTo(RunStatus.RequiresAction)); + // message += $" {requiredActionUpdate.FunctionName}"; + // toolOutputs.Add(new(requiredActionUpdate.ToolCallId, "warm and sunny")); + // } + // if (update is MessageContentUpdate contentUpdate) + // { + // message += $" {contentUpdate.Text}"; + // } + // Print(message); + // } + // if (toolOutputs.Count > 0) + // { + // asyncResults = client.SubmitToolOutputsToRunStreamingAsync(run, toolOutputs); + // } + // } while (run?.Status.IsTerminal == false); + //} [Test] public void BasicFileSearchWorks() @@ -587,16 +576,10 @@ This file describes the favorite foods of several people. Assert.That(thread.ToolResources?.FileSearch?.VectorStoreIds, Has.Count.EqualTo(1)); Assert.That(thread.ToolResources.FileSearch.VectorStoreIds[0], Is.EqualTo(createdVectorStoreId)); - ThreadRun run = client.CreateRun(thread, assistant); - Validate(run); - do - { - Thread.Sleep(1000); - run = client.GetRun(run); - } while (run?.Status.IsTerminal == false); - Assert.That(run.Status, Is.EqualTo(RunStatus.Completed)); + ThreadRunOperation runOperation = client.CreateRun(ReturnWhen.Completed, thread, assistant); + Assert.That(runOperation.Status, Is.EqualTo(RunStatus.Completed)); - PageableCollection messages = client.GetMessages(thread, resultOrder: ListOrder.NewestFirst); + IEnumerable messages = client.GetMessages(thread, new() { Order = ListOrder.NewestFirst }).GetAllValues(); foreach (ThreadMessage message in messages) { foreach (MessageContent content in message.Content) @@ -630,7 +613,7 @@ public async Task CanEnumerateAssistants() // Page through collection int count = 0; - AsyncPageableCollection assistants = client.GetAssistantsAsync(ListOrder.NewestFirst); + IAsyncEnumerable assistants = client.GetAssistantsAsync(new AssistantCollectionOptions() { Order = ListOrder.NewestFirst }).GetAllValuesAsync(); int lastIdSeen = int.MaxValue; @@ -672,14 +655,184 @@ public async Task CanPageThroughAssistantCollection() // Page through collection int count = 0; int pageCount = 0; - AsyncPageableCollection assistants = client.GetAssistantsAsync(ListOrder.NewestFirst); - IAsyncEnumerable> pages = assistants.AsPages(pageSizeHint: 2); + AsyncPageCollection pages = client.GetAssistantsAsync( + new AssistantCollectionOptions() + { + Order = ListOrder.NewestFirst, + PageSize = 2 + }); int lastIdSeen = int.MaxValue; - await foreach (ResultPage page in pages) + await foreach (PageResult page in pages) { - foreach (Assistant assistant in page) + foreach (Assistant assistant in page.Values) + { + Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); + if (assistant.Name?.StartsWith("Test Assistant ") == true) + { + Assert.That(int.TryParse(assistant.Name["Test Assistant ".Length..], out int seenId), Is.True); + Assert.That(seenId, Is.LessThan(lastIdSeen)); + lastIdSeen = seenId; + } + count++; + } + + pageCount++; + if (lastIdSeen == 0 || count > 100) + { + break; + } + } + + Assert.That(count, Is.GreaterThanOrEqualTo(10)); + Assert.That(pageCount, Is.GreaterThanOrEqualTo(5)); + } + + [Test] + public async Task CanRehydratePageCollectionFromBytes() + { + AssistantClient client = GetTestClient(); + + // Create assistant collection + for (int i = 0; i < 10; i++) + { + Assistant assistant = client.CreateAssistant("gpt-3.5-turbo", new AssistantCreationOptions() + { + Name = $"Test Assistant {i}" + }); + Validate(assistant); + Assert.That(assistant.Name, Is.EqualTo($"Test Assistant {i}")); + } + + AsyncPageCollection pages = client.GetAssistantsAsync( + new AssistantCollectionOptions() + { + Order = ListOrder.NewestFirst, + PageSize = 2 + }); + + // Simulate rehydration of the collection + // TODO: too complicated? + BinaryData rehydrationBytes = (await pages.GetCurrentPageAsync().ConfigureAwait(false)).PageToken.ToBytes(); + ContinuationToken rehydrationToken = ContinuationToken.FromBytes(rehydrationBytes); + + AsyncPageCollection rehydratedPages = client.GetAssistantsAsync(rehydrationToken); + + int count = 0; + int pageCount = 0; + int lastIdSeen = int.MaxValue; + + await foreach (PageResult page in rehydratedPages) + { + foreach (Assistant assistant in page.Values) + { + Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); + if (assistant.Name?.StartsWith("Test Assistant ") == true) + { + Assert.That(int.TryParse(assistant.Name["Test Assistant ".Length..], out int seenId), Is.True); + Assert.That(seenId, Is.LessThan(lastIdSeen)); + lastIdSeen = seenId; + } + count++; + } + + pageCount++; + if (lastIdSeen == 0 || count > 100) + { + break; + } + } + + Assert.That(count, Is.GreaterThanOrEqualTo(10)); + Assert.That(pageCount, Is.GreaterThanOrEqualTo(5)); + } + + [Test] + public async Task CanRehydratePageCollectionFromPageToken() + { + AssistantClient client = GetTestClient(); + + // Create assistant collection + for (int i = 0; i < 10; i++) + { + Assistant assistant = client.CreateAssistant("gpt-3.5-turbo", new AssistantCreationOptions() + { + Name = $"Test Assistant {i}" + }); + Validate(assistant); + Assert.That(assistant.Name, Is.EqualTo($"Test Assistant {i}")); + } + + AsyncPageCollection pages = client.GetAssistantsAsync( + new AssistantCollectionOptions() + { + Order = ListOrder.NewestFirst, + PageSize = 2 + }); + + // Call the rehydration method, passing a typed OpenAIPageToken + PageResult firstPage = await pages.GetCurrentPageAsync().ConfigureAwait(false); + AsyncPageCollection rehydratedPages = client.GetAssistantsAsync(firstPage.PageToken); + + int count = 0; + int pageCount = 0; + int lastIdSeen = int.MaxValue; + + await foreach (PageResult page in rehydratedPages) + { + foreach (Assistant assistant in page.Values) + { + Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); + if (assistant.Name?.StartsWith("Test Assistant ") == true) + { + Assert.That(int.TryParse(assistant.Name["Test Assistant ".Length..], out int seenId), Is.True); + Assert.That(seenId, Is.LessThan(lastIdSeen)); + lastIdSeen = seenId; + } + count++; + } + + pageCount++; + if (lastIdSeen == 0 || count > 100) + { + break; + } + } + + Assert.That(count, Is.GreaterThanOrEqualTo(10)); + Assert.That(pageCount, Is.GreaterThanOrEqualTo(5)); + } + + [Test] + public async Task CanCastPageCollectionToConvenienceFromProtocol() + { + AssistantClient client = GetTestClient(); + + // Create assistant collection + for (int i = 0; i < 10; i++) + { + Assistant assistant = client.CreateAssistant("gpt-3.5-turbo", new AssistantCreationOptions() + { + Name = $"Test Assistant {i}" + }); + Validate(assistant); + Assert.That(assistant.Name, Is.EqualTo($"Test Assistant {i}")); + } + + // Call the protocol method + IAsyncEnumerable pages = client.GetAssistantsAsync(limit: 2, order: "desc", after: null, before: null, options: default); + + // Cast to the convenience type + AsyncPageCollection assistantPages = (AsyncPageCollection)pages; + + int count = 0; + int pageCount = 0; + int lastIdSeen = int.MaxValue; + + await foreach (PageResult page in assistantPages) + { + foreach (Assistant assistant in page.Values) { Console.WriteLine($"[{count,3}] {assistant.Id} {assistant.CreatedAt:s} {assistant.Name}"); if (assistant.Name?.StartsWith("Test Assistant ") == true) @@ -721,7 +874,7 @@ public async Task MessagesWithRoles() async Task RefreshMessageListAsync() { messages.Clear(); - await foreach (ThreadMessage message in client.GetMessagesAsync(thread)) + await foreach (ThreadMessage message in client.GetMessagesAsync(thread).GetAllValuesAsync()) { messages.Add(message); } @@ -876,6 +1029,11 @@ private void Validate(T target) { Assert.That(run?.Id, Is.Not.Null); } + else if (target is ThreadRunOperation runOperation) + { + Assert.That(runOperation?.ThreadId, Is.Not.Null); + Assert.That(runOperation?.RunId, Is.Not.Null); + } else if (target is OpenAIFileInfo file) { Assert.That(file?.Id, Is.Not.Null); diff --git a/tests/Assistants/VectorStoreTests.cs b/tests/Assistants/VectorStoreTests.cs index 8377f7d2a..dd470f245 100644 --- a/tests/Assistants/VectorStoreTests.cs +++ b/tests/Assistants/VectorStoreTests.cs @@ -73,7 +73,7 @@ public void CanCreateGetAndDeleteVectorStores() Assert.That(deleted, Is.True); _vectorStoresToDelete.RemoveAt(_vectorStoresToDelete.Count - 1); - vectorStore = client.CreateVectorStore(new VectorStoreCreationOptions () + vectorStore = client.CreateVectorStore(new VectorStoreCreationOptions() { FileIds = testFiles.Select(file => file.Id).ToList() }); @@ -102,7 +102,7 @@ public void CanEnumerateVectorStores() int lastIdSeen = int.MaxValue; int count = 0; - foreach (VectorStore vectorStore in client.GetVectorStores(ListOrder.NewestFirst)) + foreach (VectorStore vectorStore in client.GetVectorStores(new VectorStoreCollectionOptions() { Order = ListOrder.NewestFirst }).GetAllValues()) { Assert.That(vectorStore.Id, Is.Not.Null); if (vectorStore.Name?.StartsWith("Test Vector Store ") == true) @@ -139,7 +139,7 @@ public async Task CanEnumerateVectorStoresAsync() int lastIdSeen = int.MaxValue; int count = 0; - await foreach (VectorStore vectorStore in client.GetVectorStoresAsync(ListOrder.NewestFirst)) + await foreach (VectorStore vectorStore in client.GetVectorStoresAsync(new VectorStoreCollectionOptions() { Order = ListOrder.NewestFirst }).GetAllValuesAsync()) { Assert.That(vectorStore.Id, Is.Not.Null); if (vectorStore.Name?.StartsWith("Test Vector Store ") == true) @@ -190,7 +190,7 @@ public void CanAssociateFiles() Thread.Sleep(1000); int count = 0; - foreach (VectorStoreFileAssociation association in client.GetFileAssociations(vectorStore)) + foreach (VectorStoreFileAssociation association in client.GetFileAssociations(vectorStore).GetAllValues()) { count++; Assert.That(association.FileId, Is.Not.EqualTo(files[0].Id)); @@ -223,7 +223,7 @@ public void CanUseBatchIngestion() Thread.Sleep(500); } - foreach (VectorStoreFileAssociation association in client.GetFileAssociations(batchJob)) + foreach (VectorStoreFileAssociation association in client.GetFileAssociations(batchJob).GetAllValues()) { Assert.Multiple(() => { @@ -269,9 +269,9 @@ public async Task CanApplyChunkingStrategy(ChunkingStrategyKind strategyKind) Validate(vectorStore); Assert.That(vectorStore.FileCounts.Total, Is.EqualTo(5)); - AsyncPageableCollection associations = client.GetFileAssociationsAsync(vectorStore); + AsyncPageCollection associations = client.GetFileAssociationsAsync(vectorStore); - await foreach (VectorStoreFileAssociation association in associations) + await foreach (VectorStoreFileAssociation association in associations.GetAllValuesAsync()) { Assert.That(testFiles.Any(file => file.Id == association.FileId), Is.True); Assert.That(association.ChunkingStrategy, Is.InstanceOf()); diff --git a/tests/Chat/ChatClientTests.cs b/tests/Chat/ChatClientTests.cs index 7929d2b71..7cc85239d 100644 --- a/tests/Chat/ChatClientTests.cs +++ b/tests/Chat/ChatClientTests.cs @@ -76,8 +76,8 @@ public void StreamingChat() TimeSpan? latestTokenReceiptTime = null; Stopwatch stopwatch = Stopwatch.StartNew(); - ResultCollection streamingResult = client.CompleteChatStreaming(messages); - Assert.That(streamingResult, Is.InstanceOf>()); + CollectionResult streamingResult = client.CompleteChatStreaming(messages); + Assert.That(streamingResult, Is.InstanceOf>()); int updateCount = 0; foreach (StreamingChatCompletionUpdate chatUpdate in streamingResult) @@ -108,8 +108,8 @@ public async Task StreamingChatAsync() TimeSpan? latestTokenReceiptTime = null; Stopwatch stopwatch = Stopwatch.StartNew(); - AsyncResultCollection streamingResult = client.CompleteChatStreamingAsync(messages); - Assert.That(streamingResult, Is.InstanceOf>()); + AsyncCollectionResult streamingResult = client.CompleteChatStreamingAsync(messages); + Assert.That(streamingResult, Is.InstanceOf>()); int updateCount = 0; ChatTokenUsage usage = null; @@ -336,7 +336,7 @@ public async Task TokenLogProbabilitiesStreaming(bool includeLogProbabilities) options = new(); } - AsyncResultCollection chatCompletionUpdates = client.CompleteChatStreamingAsync(messages, options); + AsyncCollectionResult chatCompletionUpdates = client.CompleteChatStreamingAsync(messages, options); Assert.That(chatCompletionUpdates, Is.Not.Null); await foreach (StreamingChatCompletionUpdate chatCompletionUpdate in chatCompletionUpdates)