diff --git a/api/OpenAI.net8.0.cs b/api/OpenAI.net8.0.cs index 57bff3c5d..7ed0660f4 100644 --- a/api/OpenAI.net8.0.cs +++ b/api/OpenAI.net8.0.cs @@ -1430,6 +1430,14 @@ public class ChatClient { public virtual Task GetChatCompletionAsync(string completionId, RequestOptions options); [Experimental("OPENAI001")] public virtual Task> GetChatCompletionAsync(string completionId, CancellationToken cancellationToken = default); + [Experimental("OPENAI001")] + public virtual CollectionResult GetChatCompletions(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); + [Experimental("OPENAI001")] + public virtual CollectionResult GetChatCompletions(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); + [Experimental("OPENAI001")] + public virtual AsyncCollectionResult GetChatCompletionsAsync(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); + [Experimental("OPENAI001")] + public virtual AsyncCollectionResult GetChatCompletionsAsync(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); } public class ChatCompletion : IJsonModel, IPersistableModel { [Experimental("OPENAI001")] @@ -1460,6 +1468,33 @@ public class ChatCompletion : IJsonModel, IPersistableModel, IPersistableModel { + public string AfterId { get; set; } + public IDictionary Metadata { get; } + public string Model { get; set; } + public ChatCompletionCollectionOrder? Order { get; set; } + public int? PageSizeLimit { get; set; } + protected virtual ChatCompletionCollectionOptions JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options); + protected virtual ChatCompletionCollectionOptions PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options); + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options); + } + [Experimental("OPENAI001")] + public readonly partial struct ChatCompletionCollectionOrder : IEquatable { + public ChatCompletionCollectionOrder(string value); + public static ChatCompletionCollectionOrder Ascending { get; } + public static ChatCompletionCollectionOrder Descending { get; } + public readonly bool Equals(ChatCompletionCollectionOrder other); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly bool Equals(object obj); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly int GetHashCode(); + public static bool operator ==(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right); + public static implicit operator ChatCompletionCollectionOrder(string value); + public static bool operator !=(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right); + public override readonly string ToString(); + } + [Experimental("OPENAI001")] public class ChatCompletionDeletionResult : IJsonModel, IPersistableModel { public string ChatCompletionId { get; } public bool Deleted { get; } diff --git a/api/OpenAI.netstandard2.0.cs b/api/OpenAI.netstandard2.0.cs index 10c9b626b..86ff44b58 100644 --- a/api/OpenAI.netstandard2.0.cs +++ b/api/OpenAI.netstandard2.0.cs @@ -1285,6 +1285,10 @@ public class ChatClient { public virtual ClientResult GetChatCompletion(string completionId, CancellationToken cancellationToken = default); public virtual Task GetChatCompletionAsync(string completionId, RequestOptions options); public virtual Task> GetChatCompletionAsync(string completionId, CancellationToken cancellationToken = default); + public virtual CollectionResult GetChatCompletions(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); + public virtual CollectionResult GetChatCompletions(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); + public virtual AsyncCollectionResult GetChatCompletionsAsync(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); + public virtual AsyncCollectionResult GetChatCompletionsAsync(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); } public class ChatCompletion : IJsonModel, IPersistableModel { public IReadOnlyList Annotations { get; } @@ -1308,6 +1312,31 @@ public class ChatCompletion : IJsonModel, IPersistableModel, IPersistableModel { + public string AfterId { get; set; } + public IDictionary Metadata { get; } + public string Model { get; set; } + public ChatCompletionCollectionOrder? Order { get; set; } + public int? PageSizeLimit { get; set; } + protected virtual ChatCompletionCollectionOptions JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options); + protected virtual ChatCompletionCollectionOptions PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options); + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options); + } + public readonly partial struct ChatCompletionCollectionOrder : IEquatable { + public ChatCompletionCollectionOrder(string value); + public static ChatCompletionCollectionOrder Ascending { get; } + public static ChatCompletionCollectionOrder Descending { get; } + public readonly bool Equals(ChatCompletionCollectionOrder other); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly bool Equals(object obj); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly int GetHashCode(); + public static bool operator ==(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right); + public static implicit operator ChatCompletionCollectionOrder(string value); + public static bool operator !=(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right); + public override readonly string ToString(); + } public class ChatCompletionDeletionResult : IJsonModel, IPersistableModel { public string ChatCompletionId { get; } public bool Deleted { get; } diff --git a/codegen/README.MD b/codegen/README.MD new file mode 100644 index 000000000..bdc587d4f --- /dev/null +++ b/codegen/README.MD @@ -0,0 +1,23 @@ +# Debugging the generator + +To configure VS Code for debugging the generator, specifically visitors, add the following to your `launch.json` in the root of the workspace + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug OpenAI Library Plugin", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceFolder}/codegen/dist/generator/Microsoft.TypeSpec.Generator.dll", + "${workspaceFolder}", + "-g", + "OpenAILibraryGenerator" + ], + } + ] +} +``` \ No newline at end of file diff --git a/codegen/generator/src/OpenAILibraryGenerator.cs b/codegen/generator/src/OpenAILibraryGenerator.cs index 9915ab0a3..5f33fc4e1 100644 --- a/codegen/generator/src/OpenAILibraryGenerator.cs +++ b/codegen/generator/src/OpenAILibraryGenerator.cs @@ -38,6 +38,8 @@ protected override void Configure() AddVisitor(new ModelSerializationVisitor()); AddVisitor(new ExperimentalAttributeVisitor()); AddVisitor(new ModelDirectoryVisitor()); + AddVisitor(new PaginationVisitor()); + AddVisitor(new MetadataQueryParamVisitor()); } } } \ No newline at end of file diff --git a/codegen/generator/src/Visitors/ExplicitConversionFromClientResultVisitor.cs b/codegen/generator/src/Visitors/ExplicitConversionFromClientResultVisitor.cs index 84fe172e1..4a5a7d249 100644 --- a/codegen/generator/src/Visitors/ExplicitConversionFromClientResultVisitor.cs +++ b/codegen/generator/src/Visitors/ExplicitConversionFromClientResultVisitor.cs @@ -15,7 +15,8 @@ protected override MethodProvider VisitMethod(MethodProvider method) if (method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Explicit) && method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Operator) && method.Signature.Parameters.Count == 1 && - method.Signature.Parameters[0].Type.Name == nameof(ClientResult)) + method.Signature.Parameters[0].Type.Name == nameof(ClientResult) && + !method.EnclosingType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)) { return null; } diff --git a/codegen/generator/src/Visitors/MetadataQueryParamVisitor.cs b/codegen/generator/src/Visitors/MetadataQueryParamVisitor.cs new file mode 100644 index 000000000..dd764ad09 --- /dev/null +++ b/codegen/generator/src/Visitors/MetadataQueryParamVisitor.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.TypeSpec.Generator.ClientModel; +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; +using Microsoft.TypeSpec.Generator.Statements; +using static OpenAILibraryPlugin.Visitors.VisitorHelpers; + +namespace OpenAILibraryPlugin.Visitors; + +/// +/// This visitor fixes up usage of the metadata query parameter into the proper format. +/// +public class MetadataQueryParamVisitor : ScmLibraryVisitor +{ + + private static readonly string[] _chatParamsToReplace = ["after", "before", "limit", "order", "model", "metadata"]; + + /// + /// Visits Create*Request methods to modify how metadata query parameters are handled. + /// It replaces the following statements: + /// + /// List list = new List(); + /// foreach (var @param in metadata) + /// { + /// uri.AppendQuery($"metadata[{@param.Key}]", @param.Value, true); + /// list.Add(@param.Key); + /// list.Add(@param.Value); + /// } + /// uri.AppendQueryDelimited("metadata", list, ",", null, true); + /// + /// with: + /// + /// foreach (var @param in metadata) + /// { + /// uri.AppendQuery($"metadata[{@param.Key}]", @param.Value, true); + /// } + /// + /// + /// + protected override MethodProvider? VisitMethod(MethodProvider method) + { + // Check if the method is one of the Create*Request methods and has a signature that takes a metadata parameter like IDictionary metadata + if (method.Signature.Name.StartsWith("Create") && method.Signature.Name.EndsWith("Request") && + method.Signature.Parameters.Any(p => p.Type.IsDictionary && p.Name == "metadata")) + { + ValueExpression? uri = null; + var statements = method.BodyStatements?.ToList() ?? new List(); + VisitExplodedMethodBodyStatements( + statements!, + statement => + { + // Check if the statement is an assignment to a variable named "uri" + // Capture it if so + if (statement is ExpressionStatement expressionStatement && + expressionStatement.Expression is AssignmentExpression assignmentExpression && + assignmentExpression.Variable is DeclarationExpression declarationExpression && + declarationExpression.Variable is VariableExpression variableExpression && + variableExpression.Declaration.RequestedName == "uri") + { + uri = variableExpression; + } + // Try to remove the unnecessary list declaration + if (statement is ExpressionStatement expressionStatement2 && + expressionStatement2.Expression is AssignmentExpression assignmentExpression2 && + assignmentExpression2.Variable is DeclarationExpression declarationExpression2 && + declarationExpression2.Variable is VariableExpression variableExpression2 && + variableExpression2.Declaration.RequestedName == "list" && + variableExpression2.Type.IsCollection && variableExpression2.Type.IsGenericType) + { + // Remove the list declaration + return new SingleLineCommentStatement("Plugin customization: remove unnecessary list declaration"); + } + + if (uri is not null && + statement is ForEachStatement foreachStatement && + foreachStatement.Enumerable is DictionaryExpression dictionaryExpression && + dictionaryExpression.Original is VariableExpression variable && + variable.Declaration.RequestedName == "metadata") + { + var formatString = new FormattableStringExpression("metadata[{0}]", [foreachStatement.ItemVariable.Property("Key")]); + var appendQueryStatement = uri.Invoke("AppendQuery", [formatString, foreachStatement.ItemVariable.Property("Value"), Snippet.True]); + foreachStatement.Body.Clear(); + foreachStatement.Body.Add(new SingleLineCommentStatement("Plugin customization: Properly handle metadata query parameters")); + foreachStatement.Body.Add(appendQueryStatement.Terminate()); + } + + // Remove the call to AppendQueryDelimited for metadata + if (statement is ExpressionStatement expressionStatement3 && + expressionStatement3.Expression is InvokeMethodExpression invokeMethodExpression && + invokeMethodExpression.MethodName == "AppendQueryDelimited" && + invokeMethodExpression.Arguments.Count == 5 && + invokeMethodExpression.Arguments[0].ToDisplayString() == "\"metadata\"") + { + return new SingleLineCommentStatement("Plugin customization: remove unnecessary AppendQueryDelimited for metadata"); + } + return statement; + }); + + // Rebuild the method body with the modified statements + method.Update(bodyStatements: statements); + } + + return base.VisitMethod(method); + } +} \ No newline at end of file diff --git a/codegen/generator/src/Visitors/PaginationVisitor.cs b/codegen/generator/src/Visitors/PaginationVisitor.cs new file mode 100644 index 000000000..c921f8a65 --- /dev/null +++ b/codegen/generator/src/Visitors/PaginationVisitor.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.TypeSpec.Generator.ClientModel; +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; +using Microsoft.TypeSpec.Generator.Statements; +using static OpenAILibraryPlugin.Visitors.VisitorHelpers; + +namespace OpenAILibraryPlugin.Visitors; + +/// +/// This visitor modifies GetRawPagesAsync methods to consider HasMore in addition to LastId when deciding whether to continue pagination. +/// It also replaces specific parameters with an options type for pagination methods. +/// +public class PaginationVisitor : ScmLibraryVisitor +{ + + private static readonly string[] _chatParamsToReplace = ["after", "before", "limit", "order", "model", "metadata"]; + private static readonly Dictionary _paramReplacementMap = new() + { + { "after", "AfterId" }, + { "before", "LastId" }, + { "limit", "PageSizeLimit" }, + { "order", "Order" }, + { "model", "Model" }, + { "metadata", "Metadata" } + }; + private static readonly Dictionary _optionsReplacements = new() + { + { + "GetChatCompletions", + ("ChatCompletion", "ChatCompletionCollectionOptions", _chatParamsToReplace) + }, + { + "GetChatCompletionsAsync", + ("ChatCompletion", "ChatCompletionCollectionOptions", _chatParamsToReplace) + } + }; + + protected override MethodProvider? VisitMethod(MethodProvider method) + { + // Try to handle pagination methods with options replacement + if (TryHandlePaginationMethodWithOptions(method)) + { + return method; + } + + // Try to handle GetRawPagesAsync methods for hasMore checks + if (TryHandleGetRawPagesAsyncMethod(method)) + { + return method; + } + + return base.VisitMethod(method); + } + + /// + /// Handles pagination methods that need their parameters replaced with an options type. + /// + /// The method to potentially handle. Will be modified in place if handling is successful. + /// True if the method was handled, false otherwise. + private bool TryHandlePaginationMethodWithOptions(MethodProvider method) + { + // Check if the method is one of the pagination methods we want to modify. + // If so, we will update its parameters to replace the specified parameters with the options type. + if (method.Signature.ReturnType is not null && + method.Signature.ReturnType.Name.EndsWith("CollectionResult") && + _optionsReplacements.TryGetValue(method.Signature.Name, out var options) && + method.Signature.ReturnType.IsGenericType && + method.Signature.ReturnType.Arguments.Count == 1 && + method.Signature.ReturnType.Arguments[0].Name == options.ReturnType) + { + var optionsType = OpenAILibraryGenerator.Instance.OutputLibrary.TypeProviders.SingleOrDefault(t => t.Type.Name == options.OptionsType); + if (optionsType is not null) + { + // replace the method parameters with names in the _paramsToReplace array with the optionsType + var methodSignature = method.Signature; + var newParameters = methodSignature.Parameters.ToList(); + int lastRemovedIndex = -1; + for (int i = 0; i < newParameters.Count; i++) + { + if (_chatParamsToReplace.Contains(newParameters[i].Name)) + { + newParameters.RemoveAt(i); + lastRemovedIndex = i; + i--; + } + } + if (lastRemovedIndex >= 0) + { + newParameters.Insert( + lastRemovedIndex, + new ParameterProvider("options", $"The pagination options", optionsType.Type, defaultValue: Snippet.Default)); + + var newSignature = new MethodSignature( + methodSignature.Name, + methodSignature.Description, + methodSignature.Modifiers, + methodSignature.ReturnType, + methodSignature.ReturnDescription, + newParameters, + methodSignature.Attributes, + methodSignature.GenericArguments, + methodSignature.GenericParameterConstraints, + methodSignature.ExplicitInterface, + methodSignature.NonDocumentComment); + + var optionsParam = newParameters[lastRemovedIndex]; + + // Update the method body statements to replace the old parameters with the new options parameter. + var statements = method.BodyStatements?.ToList() ?? new List(); + VisitExplodedMethodBodyStatements(statements!, + statement => + { + // Check if the statement is a return statement + if (statement is ExpressionStatement exp && exp.Expression is KeywordExpression keyword && keyword.Keyword == "return") + { + // If it is, we will replace the parameters with the options parameter. + if (keyword.Expression is NewInstanceExpression newInstance && + newInstance.Parameters.Count > 0) + { + // Create the new parameters with the options parameter. + var newParameters = new List(); + foreach (var param in newInstance.Parameters) + { + if (param is VariableExpression varExpr && options.ParamsToReplace.Contains(varExpr.Declaration.RequestedName)) + { + // Replace the parameter with the options parameter. + if (_paramReplacementMap.TryGetValue(varExpr.Declaration.RequestedName, out var replacement)) + { + newParameters.Add(optionsParam.NullConditional().Property(replacement)); + } + } + else if (param is InvokeMethodExpression invokeMethod && invokeMethod.MethodName == "ToString" && + invokeMethod.InstanceReference is NullConditionalExpression nullConditional && + nullConditional.Inner is VariableExpression varExpr2 && + options.ParamsToReplace.Contains(varExpr2.Declaration.RequestedName)) + { + // Replace the parameter with the options parameter. + if (_paramReplacementMap.TryGetValue(varExpr2.Declaration.RequestedName, out var replacement)) + { + newParameters.Add(optionsParam.NullConditional().Property(replacement).NullConditional().Invoke("ToString", Array.Empty())); + } + } + else + { + // Keep the original parameter. + newParameters.Add(param); + } + } + // Create a new ExpressionStatement with the same children as the original, but with the new parameters. + return Snippet.Return(Snippet.New.Instance(newInstance.Type!, newParameters)); + } + } + return statement; + }); + + method.Update(signature: newSignature, bodyStatements: statements); + return true; + } + } + } + + return false; + } + + /// + /// Handles GetRawPagesAsync methods to add hasMore == false checks for pagination. + /// + /// The method to potentially handle. Will be modified in place if handling is successful. + /// True if the method was handled, false otherwise. + private bool TryHandleGetRawPagesAsyncMethod(MethodProvider method) + { + // If the method is GetRawPagesAsync and is internal, we will modify the body statements to add a check for hasMore == false. + // This is to ensure that pagination stops when hasMore is false, in addition to checking LastId. + if (method.Signature.Name == "GetRawPagesAsync" && method.EnclosingType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)) + { + var statements = method.BodyStatements?.ToList() ?? new List(); + VisitExplodedMethodBodyStatements( + statements!, + statement => + { + if (statement is IfStatement) + { + return GetUpdatedIfStatement( + statement, + expression => + { + // Check if this is a binary expression with "==" operator + if (expression is ScopedApi scopedApi && scopedApi.Original is BinaryOperatorExpression binaryExpr && binaryExpr.Operator == "==") + { + // Check if left side is "nextToken" and right side is "null" + if (binaryExpr.Left is VariableExpression leftVar && + leftVar.Declaration.RequestedName == "nextToken" && + binaryExpr.Right is KeywordExpression rightKeyword && + rightKeyword.Keyword == "null") + { + // Create "hasMore == null" condition + var hasMoreNullCheck = new BinaryOperatorExpression( + "==", + new MemberExpression(null, "hasMore"), + Snippet.False); + + // Return "nextToken == null || hasMore == null" + return new BinaryOperatorExpression("||", binaryExpr, hasMoreNullCheck); + } + } + return expression; + }, + "Plugin customization: add hasMore == false check to pagination condition"); + } + else if (statement is WhileStatement whileStatement) + { + var statementList = whileStatement.Body + .SelectMany(bodyStatement => bodyStatement) + .ToList(); + + // Check for the assignment of nextToken and add hasMore assignment + for (int i = 0; i < statementList.Count; i++) + { + if (statementList[i] is ExpressionStatement expressionStatement && + expressionStatement.Expression is AssignmentExpression assignmentExpression && + assignmentExpression.Variable is VariableExpression variableExpression && + variableExpression.Declaration.RequestedName == "nextToken" && + assignmentExpression.Value is MemberExpression memberExpression && + memberExpression.MemberName == "LastId") + { + // Create a new assignment for hasMore + var hasMoreAssignment = new AssignmentExpression( + new DeclarationExpression(typeof(bool), "hasMore"), + new MemberExpression(memberExpression.Inner, "HasMore")); + + // Insert the new assignment before the existing one + statementList.Insert(i, hasMoreAssignment.Terminate()); + statementList.Insert(i, new SingleLineCommentStatement("Plugin customization: add hasMore assignment")); + var updatedWhileStatement = new WhileStatement(whileStatement.Condition); + foreach (MethodBodyStatement bodyStatement in statementList) + { + updatedWhileStatement.Add(bodyStatement); + } + return updatedWhileStatement; + } + } + } + return statement; + }); + + method.Update(bodyStatements: statements); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/codegen/generator/src/Visitors/VisitorHelpers.cs b/codegen/generator/src/Visitors/VisitorHelpers.cs index 7775bc709..37b7dedae 100644 --- a/codegen/generator/src/Visitors/VisitorHelpers.cs +++ b/codegen/generator/src/Visitors/VisitorHelpers.cs @@ -16,7 +16,7 @@ internal static void VisitExplodedMethodBodyStatements( for (int i = 0; i < statements.Count; i++) { statements[i] = visitorFunc.Invoke(statements[i]); - + if (statements[i] is ForEachStatement foreachStatement) { List foreachBodyStatements @@ -29,12 +29,36 @@ List foreachBodyStatements } else if (statements[i] is IfStatement ifStatement) { - // To do: traverse inside of "if" + List ifBodyStatements + = ifStatement.Body + .SelectMany(bodyStatement => bodyStatement) + .ToList(); + VisitExplodedMethodBodyStatements(ifBodyStatements!, visitorFunc); + var newIfStatement = new IfStatement(ifStatement.Condition); + foreach (MethodBodyStatement bodyStatement in ifBodyStatements) + { + newIfStatement.Add(bodyStatement); + } + statements[i] = newIfStatement; } else if (statements[i] is ForStatement forStatement) { // To do: traverse inside of "for" } + else if (statements[i] is WhileStatement whileStatement) + { + List whileBodyStatements + = whileStatement.Body + .SelectMany(bodyStatement => bodyStatement) + .ToList(); + VisitExplodedMethodBodyStatements(whileBodyStatements!, visitorFunc); + var newWhileStatement = new WhileStatement(whileStatement.Condition); + foreach (MethodBodyStatement bodyStatement in whileBodyStatements) + { + newWhileStatement.Add(bodyStatement); + } + statements[i] = newWhileStatement; + } } } diff --git a/specification/client/models/chat.models.tsp b/specification/client/models/chat.models.tsp new file mode 100644 index 000000000..8f239b501 --- /dev/null +++ b/specification/client/models/chat.models.tsp @@ -0,0 +1,34 @@ +import "../../base/typespec/chat/main.tsp"; +import "@azure-tools/typespec-client-generator-core"; + +using Azure.ClientGenerator.Core; +using TypeSpec.Http; + +namespace OpenAI; + + +alias ChatCompletionCollectionOrderQueryParameter = { + /** + * Sort order by the `created_at` timestamp of the objects. `asc` for ascending order and`desc` + * for descending order. + */ + @query order?: ChatCompletionCollectionOrder; +}; + +union ChatCompletionCollectionOrder { + string, + Ascending: "asc", + Descending: "desc", +} + +@access(Access.public) +@usage(Usage.input) +model ChatCompletionCollectionOptions { + ...CollectionAfterQueryParameter, + ...CollectionLimitQueryParameter, + ...ChatCompletionCollectionOrderQueryParameter, + @query metadata?: Record, + @query `model`?: string, +} + + diff --git a/specification/client/models/common.models.tsp b/specification/client/models/common.models.tsp index 2ea91f3da..7d3a74992 100644 --- a/specification/client/models/common.models.tsp +++ b/specification/client/models/common.models.tsp @@ -1,7 +1,9 @@ import "../../base/typespec/common/main.tsp"; import "@azure-tools/typespec-client-generator-core"; +import "@typespec/http"; using Azure.ClientGenerator.Core; +using TypeSpec.Http; namespace OpenAI; @@ -124,3 +126,22 @@ union DotNetChatVoiceIds { union DotNetRealtimeVoiceIds { VoiceIdsShared, } + +// CollectionQueryParameters + +alias CollectionLimitQueryParameter = { + /** + * A limit on the number of objects to be returned. Limit can range between 1 and 100, and the + * default is 20. + */ + @query pageSizeLimit?: int32 = 20; +}; + +alias CollectionAfterQueryParameter = { + /** + * A cursor for use in pagination. `after` is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + * subsequent call can include after=obj_foo in order to fetch the next page of the list. + */ + @query @continuationToken afterId?: string; +}; \ No newline at end of file diff --git a/specification/main.tsp b/specification/main.tsp index 609d39ffb..db58af760 100644 --- a/specification/main.tsp +++ b/specification/main.tsp @@ -15,6 +15,7 @@ import "./client/threads.client.tsp"; import "./client/vector-stores.client.tsp"; import "./client/models/audio.models.tsp"; +import "./client/models/chat.models.tsp"; import "./client/models/common.models.tsp"; import "./client/models/responses.models.tsp"; import "./client/models/vector-stores.models.tsp"; diff --git a/src/Custom/Chat/ChatClient.Protocol.cs b/src/Custom/Chat/ChatClient.Protocol.cs index 40c25a767..30d6ab2ac 100644 --- a/src/Custom/Chat/ChatClient.Protocol.cs +++ b/src/Custom/Chat/ChatClient.Protocol.cs @@ -9,8 +9,6 @@ namespace OpenAI.Chat; /// The service client for the OpenAI Chat Completions endpoint. [CodeGenSuppress("GetChatCompletionMessagesAsync", typeof(string), typeof(string), typeof(int?), typeof(string), typeof(RequestOptions))] [CodeGenSuppress("GetChatCompletionMessages", typeof(string), typeof(string), typeof(int?), typeof(string), typeof(RequestOptions))] -[CodeGenSuppress("GetChatCompletionsAsync", typeof(string), typeof(int?), typeof(string), typeof(IDictionary), typeof(string), typeof(RequestOptions))] -[CodeGenSuppress("GetChatCompletions", typeof(string), typeof(int?), typeof(string), typeof(IDictionary), typeof(string), typeof(RequestOptions))] [CodeGenSuppress("UpdateChatCompletionAsync", typeof(string), typeof(BinaryContent), typeof(RequestOptions))] [CodeGenSuppress("UpdateChatCompletion", typeof(string), typeof(BinaryContent), typeof(RequestOptions))] public partial class ChatClient diff --git a/src/Custom/Chat/ChatClient.cs b/src/Custom/Chat/ChatClient.cs index f88d4bfe8..17352d668 100644 --- a/src/Custom/Chat/ChatClient.cs +++ b/src/Custom/Chat/ChatClient.cs @@ -22,8 +22,6 @@ namespace OpenAI.Chat; [CodeGenSuppress("CompleteChatAsync", typeof(ChatCompletionOptions), typeof(CancellationToken))] [CodeGenSuppress("GetChatCompletionMessages", typeof(string), typeof(string), typeof(int?), typeof(OpenAI.VectorStores.VectorStoreCollectionOrder?), typeof(CancellationToken))] [CodeGenSuppress("GetChatCompletionMessagesAsync", typeof(string), typeof(string), typeof(int?), typeof(OpenAI.VectorStores.VectorStoreCollectionOrder?), typeof(CancellationToken))] -[CodeGenSuppress("GetChatCompletions", typeof(string), typeof(int?), typeof(OpenAI.VectorStores.VectorStoreCollectionOrder?), typeof(IDictionary), typeof(string), typeof(CancellationToken))] -[CodeGenSuppress("GetChatCompletionsAsync", typeof(string), typeof(int?), typeof(OpenAI.VectorStores.VectorStoreCollectionOrder?), typeof(IDictionary), typeof(string), typeof(CancellationToken))] [CodeGenSuppress("UpdateChatCompletion", typeof(string), typeof(IDictionary), typeof(CancellationToken))] [CodeGenSuppress("UpdateChatCompletionAsync", typeof(string), typeof(IDictionary), typeof(CancellationToken))] public partial class ChatClient diff --git a/src/Custom/Chat/ChatCompletionCollectionOptions.cs b/src/Custom/Chat/ChatCompletionCollectionOptions.cs new file mode 100644 index 000000000..be39318be --- /dev/null +++ b/src/Custom/Chat/ChatCompletionCollectionOptions.cs @@ -0,0 +1,4 @@ +namespace OpenAI.Chat; + +// CUSTOM: Make public and use the correct namespace. +[CodeGenType("ChatCompletionCollectionOptions")] public partial class ChatCompletionCollectionOptions { } \ No newline at end of file diff --git a/src/Custom/Chat/ChatCompletionCollectionOrder.cs b/src/Custom/Chat/ChatCompletionCollectionOrder.cs new file mode 100644 index 000000000..3dc393eeb --- /dev/null +++ b/src/Custom/Chat/ChatCompletionCollectionOrder.cs @@ -0,0 +1,4 @@ +namespace OpenAI.Chat; + +// CUSTOM: Make public and use the correct namespace. +[CodeGenType("ChatCompletionCollectionOrder")] public readonly partial struct ChatCompletionCollectionOrder { } \ No newline at end of file diff --git a/src/Custom/Files/Internal/GeneratorStubs.cs b/src/Custom/Files/Internal/GeneratorStubs.cs index cf854c952..a85d22a66 100644 --- a/src/Custom/Files/Internal/GeneratorStubs.cs +++ b/src/Custom/Files/Internal/GeneratorStubs.cs @@ -38,12 +38,6 @@ public static implicit operator BinaryContent(InternalCreateUploadRequest intern [CodeGenType("Upload")] internal partial class InternalUpload { - public static explicit operator InternalUpload(ClientResult result) - { - using PipelineResponse response = result.GetRawResponse(); - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeInternalUpload(document.RootElement, ModelSerializationExtensions.WireOptions); - } } [CodeGenType("UploadObject")] internal readonly partial struct InternalUploadObject { } [CodeGenType("UploadPart")] internal partial class InternalUploadPart { } diff --git a/src/Custom/LegacyCompletions/Internal/GeneratorStubs.cs b/src/Custom/LegacyCompletions/Internal/GeneratorStubs.cs index aeaecf512..744cad848 100644 --- a/src/Custom/LegacyCompletions/Internal/GeneratorStubs.cs +++ b/src/Custom/LegacyCompletions/Internal/GeneratorStubs.cs @@ -26,12 +26,6 @@ internal readonly partial struct InternalCreateCompletionRequestModel { } [CodeGenType("CreateCompletionResponse")] internal partial class InternalCreateCompletionResponse { - public static explicit operator InternalCreateCompletionResponse(ClientResult result) - { - using PipelineResponse response = result.GetRawResponse(); - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeInternalCreateCompletionResponse(document.RootElement, ModelSerializationExtensions.WireOptions); - } } [CodeGenType("CreateCompletionResponseChoice")] diff --git a/src/Generated/ChatClient.RestClient.cs b/src/Generated/ChatClient.RestClient.cs index c62992313..898dda5a3 100644 --- a/src/Generated/ChatClient.RestClient.cs +++ b/src/Generated/ChatClient.RestClient.cs @@ -38,13 +38,13 @@ internal virtual PipelineMessage CreateGetChatCompletionsRequest(string after, i } if (metadata != null && !(metadata is ChangeTrackingDictionary changeTrackingDictionary && changeTrackingDictionary.IsUndefined)) { - List list = new List(); + // Plugin customization: remove unnecessary list declaration foreach (var @param in metadata) { - list.Add(@param.Key); - list.Add(@param.Value); + // Plugin customization: Properly handle metadata query parameters + uri.AppendQuery($"metadata[{@param.Key}]", @param.Value, true); } - uri.AppendQueryDelimited("metadata", list, ",", null, true); + // Plugin customization: remove unnecessary AppendQueryDelimited for metadata } if (model != null) { diff --git a/src/Generated/ChatClient.cs b/src/Generated/ChatClient.cs index 5facc9551..7dcf1b723 100644 --- a/src/Generated/ChatClient.cs +++ b/src/Generated/ChatClient.cs @@ -5,6 +5,9 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using OpenAI; @@ -20,6 +23,58 @@ protected ChatClient() public ClientPipeline Pipeline { get; } + [Experimental("OPENAI001")] + public virtual CollectionResult GetChatCompletions(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + return new ChatClientGetChatCompletionsCollectionResult( + this, + after, + limit, + order, + metadata, + model, + options); + } + + [Experimental("OPENAI001")] + public virtual AsyncCollectionResult GetChatCompletionsAsync(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + return new ChatClientGetChatCompletionsAsyncCollectionResult( + this, + after, + limit, + order, + metadata, + model, + options); + } + + [Experimental("OPENAI001")] + public virtual CollectionResult GetChatCompletions(ChatCompletionCollectionOptions options = default, CancellationToken cancellationToken = default) + { + return new ChatClientGetChatCompletionsCollectionResultOfT( + this, + options?.AfterId, + options?.PageSizeLimit, + options?.Order?.ToString(), + options?.Metadata, + options?.Model, + cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null); + } + + [Experimental("OPENAI001")] + public virtual AsyncCollectionResult GetChatCompletionsAsync(ChatCompletionCollectionOptions options = default, CancellationToken cancellationToken = default) + { + return new ChatClientGetChatCompletionsAsyncCollectionResultOfT( + this, + options?.AfterId, + options?.PageSizeLimit, + options?.Order?.ToString(), + options?.Metadata, + options?.Model, + cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null); + } + public virtual ClientResult CompleteChat(BinaryContent content, RequestOptions options = null) { Argument.AssertNotNull(content, nameof(content)); diff --git a/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResult.cs b/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResult.cs new file mode 100644 index 000000000..5740521e7 --- /dev/null +++ b/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResult.cs @@ -0,0 +1,67 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace OpenAI.Chat +{ + internal partial class ChatClientGetChatCompletionsAsyncCollectionResult : AsyncCollectionResult + { + private readonly ChatClient _client; + private readonly string _after; + private readonly int? _limit; + private readonly string _order; + private readonly IDictionary _metadata; + private readonly string _model; + private readonly RequestOptions _options; + + public ChatClientGetChatCompletionsAsyncCollectionResult(ChatClient client, string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + _client = client; + _after = after; + _limit = limit; + _order = order; + _metadata = metadata; + _model = model; + _options = options; + } + + public override async IAsyncEnumerable GetRawPagesAsync() + { + PipelineMessage message = _client.CreateGetChatCompletionsRequest(_after, _limit, _order, _metadata, _model, _options); + string nextToken = null; + while (true) + { + ClientResult result = ClientResult.FromResponse(await _client.Pipeline.ProcessMessageAsync(message, _options).ConfigureAwait(false)); + yield return result; + + // Plugin customization: add hasMore assignment + bool hasMore = ((InternalChatCompletionList)result).HasMore; + nextToken = ((InternalChatCompletionList)result).LastId; + // Plugin customization: add hasMore == false check to pagination condition + if (nextToken == null || hasMore == false) + { + yield break; + } + message = _client.CreateGetChatCompletionsRequest(nextToken, _limit, _order, _metadata, _model, _options); + } + } + + public override ContinuationToken GetContinuationToken(ClientResult page) + { + string nextPage = ((InternalChatCompletionList)page).LastId; + if (nextPage != null) + { + return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); + } + else + { + return null; + } + } + } +} diff --git a/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResultOfT.cs b/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResultOfT.cs new file mode 100644 index 000000000..d486452b1 --- /dev/null +++ b/src/Generated/ChatClientGetChatCompletionsAsyncCollectionResultOfT.cs @@ -0,0 +1,77 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace OpenAI.Chat +{ + internal partial class ChatClientGetChatCompletionsAsyncCollectionResultOfT : AsyncCollectionResult + { + private readonly ChatClient _client; + private readonly string _after; + private readonly int? _limit; + private readonly string _order; + private readonly IDictionary _metadata; + private readonly string _model; + private readonly RequestOptions _options; + + public ChatClientGetChatCompletionsAsyncCollectionResultOfT(ChatClient client, string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + _client = client; + _after = after; + _limit = limit; + _order = order; + _metadata = metadata; + _model = model; + _options = options; + } + + public override async IAsyncEnumerable GetRawPagesAsync() + { + PipelineMessage message = _client.CreateGetChatCompletionsRequest(_after, _limit, _order, _metadata, _model, _options); + string nextToken = null; + while (true) + { + ClientResult result = ClientResult.FromResponse(await _client.Pipeline.ProcessMessageAsync(message, _options).ConfigureAwait(false)); + yield return result; + + // Plugin customization: add hasMore assignment + bool hasMore = ((InternalChatCompletionList)result).HasMore; + nextToken = ((InternalChatCompletionList)result).LastId; + // Plugin customization: add hasMore == false check to pagination condition + if (nextToken == null || hasMore == false) + { + yield break; + } + message = _client.CreateGetChatCompletionsRequest(nextToken, _limit, _order, _metadata, _model, _options); + } + } + + public override ContinuationToken GetContinuationToken(ClientResult page) + { + string nextPage = ((InternalChatCompletionList)page).LastId; + if (nextPage != null) + { + return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); + } + else + { + return null; + } + } + + protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) + { + foreach (ChatCompletion item in ((InternalChatCompletionList)page).Data) + { + yield return item; + await Task.Yield(); + } + } + } +} diff --git a/src/Generated/ChatClientGetChatCompletionsCollectionResult.cs b/src/Generated/ChatClientGetChatCompletionsCollectionResult.cs new file mode 100644 index 000000000..f62c24cc9 --- /dev/null +++ b/src/Generated/ChatClientGetChatCompletionsCollectionResult.cs @@ -0,0 +1,64 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace OpenAI.Chat +{ + internal partial class ChatClientGetChatCompletionsCollectionResult : CollectionResult + { + private readonly ChatClient _client; + private readonly string _after; + private readonly int? _limit; + private readonly string _order; + private readonly IDictionary _metadata; + private readonly string _model; + private readonly RequestOptions _options; + + public ChatClientGetChatCompletionsCollectionResult(ChatClient client, string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + _client = client; + _after = after; + _limit = limit; + _order = order; + _metadata = metadata; + _model = model; + _options = options; + } + + public override IEnumerable GetRawPages() + { + PipelineMessage message = _client.CreateGetChatCompletionsRequest(_after, _limit, _order, _metadata, _model, _options); + string nextToken = null; + while (true) + { + ClientResult result = ClientResult.FromResponse(_client.Pipeline.ProcessMessage(message, _options)); + yield return result; + + nextToken = ((InternalChatCompletionList)result).LastId; + if (nextToken == null) + { + yield break; + } + message = _client.CreateGetChatCompletionsRequest(nextToken, _limit, _order, _metadata, _model, _options); + } + } + + public override ContinuationToken GetContinuationToken(ClientResult page) + { + string nextPage = ((InternalChatCompletionList)page).LastId; + if (nextPage != null) + { + return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); + } + else + { + return null; + } + } + } +} diff --git a/src/Generated/ChatClientGetChatCompletionsCollectionResultOfT.cs b/src/Generated/ChatClientGetChatCompletionsCollectionResultOfT.cs new file mode 100644 index 000000000..9e3ba0378 --- /dev/null +++ b/src/Generated/ChatClientGetChatCompletionsCollectionResultOfT.cs @@ -0,0 +1,69 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace OpenAI.Chat +{ + internal partial class ChatClientGetChatCompletionsCollectionResultOfT : CollectionResult + { + private readonly ChatClient _client; + private readonly string _after; + private readonly int? _limit; + private readonly string _order; + private readonly IDictionary _metadata; + private readonly string _model; + private readonly RequestOptions _options; + + public ChatClientGetChatCompletionsCollectionResultOfT(ChatClient client, string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options) + { + _client = client; + _after = after; + _limit = limit; + _order = order; + _metadata = metadata; + _model = model; + _options = options; + } + + public override IEnumerable GetRawPages() + { + PipelineMessage message = _client.CreateGetChatCompletionsRequest(_after, _limit, _order, _metadata, _model, _options); + string nextToken = null; + while (true) + { + ClientResult result = ClientResult.FromResponse(_client.Pipeline.ProcessMessage(message, _options)); + yield return result; + + nextToken = ((InternalChatCompletionList)result).LastId; + if (nextToken == null) + { + yield break; + } + message = _client.CreateGetChatCompletionsRequest(nextToken, _limit, _order, _metadata, _model, _options); + } + } + + public override ContinuationToken GetContinuationToken(ClientResult page) + { + string nextPage = ((InternalChatCompletionList)page).LastId; + if (nextPage != null) + { + return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); + } + else + { + return null; + } + } + + protected override IEnumerable GetValuesFromPage(ClientResult page) + { + return ((InternalChatCompletionList)page).Data; + } + } +} diff --git a/src/Generated/Models/Assistants/InternalListAssistantsResponse.Serialization.cs b/src/Generated/Models/Assistants/InternalListAssistantsResponse.Serialization.cs index 355041181..dd0e50630 100644 --- a/src/Generated/Models/Assistants/InternalListAssistantsResponse.Serialization.cs +++ b/src/Generated/Models/Assistants/InternalListAssistantsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListAssistantsResponse PersistableModelCreateCore(Bina } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListAssistantsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListAssistantsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Assistants/InternalListMessagesResponse.Serialization.cs b/src/Generated/Models/Assistants/InternalListMessagesResponse.Serialization.cs index 089a43108..a4ce553b5 100644 --- a/src/Generated/Models/Assistants/InternalListMessagesResponse.Serialization.cs +++ b/src/Generated/Models/Assistants/InternalListMessagesResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListMessagesResponse PersistableModelCreateCore(Binary } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListMessagesResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListMessagesResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Assistants/InternalListRunStepsResponse.Serialization.cs b/src/Generated/Models/Assistants/InternalListRunStepsResponse.Serialization.cs index 1709103c6..739661052 100644 --- a/src/Generated/Models/Assistants/InternalListRunStepsResponse.Serialization.cs +++ b/src/Generated/Models/Assistants/InternalListRunStepsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListRunStepsResponse PersistableModelCreateCore(Binary } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListRunStepsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListRunStepsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Assistants/InternalListRunsResponse.Serialization.cs b/src/Generated/Models/Assistants/InternalListRunsResponse.Serialization.cs index a02f126fd..6abb47d79 100644 --- a/src/Generated/Models/Assistants/InternalListRunsResponse.Serialization.cs +++ b/src/Generated/Models/Assistants/InternalListRunsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListRunsResponse PersistableModelCreateCore(BinaryData } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListRunsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListRunsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Batch/InternalBatchJob.Serialization.cs b/src/Generated/Models/Batch/InternalBatchJob.Serialization.cs index b63b87acd..7a87b100f 100644 --- a/src/Generated/Models/Batch/InternalBatchJob.Serialization.cs +++ b/src/Generated/Models/Batch/InternalBatchJob.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -428,5 +429,12 @@ protected virtual InternalBatchJob PersistableModelCreateCore(BinaryData data, M } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalBatchJob(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalBatchJob(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Batch/InternalListBatchesResponse.Serialization.cs b/src/Generated/Models/Batch/InternalListBatchesResponse.Serialization.cs index 45fe4af08..4bc86c18f 100644 --- a/src/Generated/Models/Batch/InternalListBatchesResponse.Serialization.cs +++ b/src/Generated/Models/Batch/InternalListBatchesResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalListBatchesResponse PersistableModelCreateCore(BinaryD } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListBatchesResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListBatchesResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Chat/ChatCompletionCollectionOptions.Serialization.cs b/src/Generated/Models/Chat/ChatCompletionCollectionOptions.Serialization.cs new file mode 100644 index 000000000..44c2b99ba --- /dev/null +++ b/src/Generated/Models/Chat/ChatCompletionCollectionOptions.Serialization.cs @@ -0,0 +1,123 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using OpenAI; + +namespace OpenAI.Chat +{ + public partial class ChatCompletionCollectionOptions : IJsonModel + { + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(ChatCompletionCollectionOptions)} does not support writing '{format}' format."); + } + // Plugin customization: remove options.Format != "W" check + if (_additionalBinaryDataProperties != null) + { + foreach (var item in _additionalBinaryDataProperties) + { + if (ModelSerializationExtensions.IsSentinelValue(item.Value)) + { + continue; + } + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + ChatCompletionCollectionOptions IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + protected virtual ChatCompletionCollectionOptions JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(ChatCompletionCollectionOptions)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeChatCompletionCollectionOptions(document.RootElement, options); + } + + internal static ChatCompletionCollectionOptions DeserializeChatCompletionCollectionOptions(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + string afterId = default; + int? pageSizeLimit = default; + ChatCompletionCollectionOrder? order = default; + IDictionary metadata = default; + string model = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + // Plugin customization: remove options.Format != "W" check + additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); + } + return new ChatCompletionCollectionOptions( + afterId, + pageSizeLimit, + order, + metadata ?? new ChangeTrackingDictionary(), + model, + additionalBinaryDataProperties); + } + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options, OpenAIContext.Default); + default: + throw new FormatException($"The model {nameof(ChatCompletionCollectionOptions)} does not support writing '{options.Format}' format."); + } + } + + ChatCompletionCollectionOptions IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + protected virtual ChatCompletionCollectionOptions PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data)) + { + return DeserializeChatCompletionCollectionOptions(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(ChatCompletionCollectionOptions)} does not support reading '{options.Format}' format."); + } + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + } +} diff --git a/src/Generated/Models/Chat/ChatCompletionCollectionOptions.cs b/src/Generated/Models/Chat/ChatCompletionCollectionOptions.cs new file mode 100644 index 000000000..8b5aaa219 --- /dev/null +++ b/src/Generated/Models/Chat/ChatCompletionCollectionOptions.cs @@ -0,0 +1,48 @@ +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using OpenAI; + +namespace OpenAI.Chat +{ + [Experimental("OPENAI001")] + public partial class ChatCompletionCollectionOptions + { + private protected IDictionary _additionalBinaryDataProperties; + + public ChatCompletionCollectionOptions() : this(null, default, default, null, null, null) + { + } + + internal ChatCompletionCollectionOptions(string afterId, int? pageSizeLimit, ChatCompletionCollectionOrder? order, IDictionary metadata, string model, IDictionary additionalBinaryDataProperties) + { + // Plugin customization: ensure initialization of collections + AfterId = afterId; + PageSizeLimit = pageSizeLimit; + Order = order; + Metadata = metadata ?? new ChangeTrackingDictionary(); + Model = model; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + public string AfterId { get; set; } + + public int? PageSizeLimit { get; set; } + + public ChatCompletionCollectionOrder? Order { get; set; } + + public IDictionary Metadata { get; } + + public string Model { get; set; } + + internal IDictionary SerializedAdditionalRawData + { + get => _additionalBinaryDataProperties; + set => _additionalBinaryDataProperties = value; + } + } +} diff --git a/src/Generated/Models/Chat/ChatCompletionCollectionOrder.cs b/src/Generated/Models/Chat/ChatCompletionCollectionOrder.cs new file mode 100644 index 000000000..414bb39bb --- /dev/null +++ b/src/Generated/Models/Chat/ChatCompletionCollectionOrder.cs @@ -0,0 +1,46 @@ +// + +#nullable disable + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using OpenAI; + +namespace OpenAI.Chat +{ + [Experimental("OPENAI001")] + public readonly partial struct ChatCompletionCollectionOrder : IEquatable + { + private readonly string _value; + private const string AscendingValue = "asc"; + private const string DescendingValue = "desc"; + + public ChatCompletionCollectionOrder(string value) + { + Argument.AssertNotNull(value, nameof(value)); + + _value = value; + } + + public static ChatCompletionCollectionOrder Ascending { get; } = new ChatCompletionCollectionOrder(AscendingValue); + + public static ChatCompletionCollectionOrder Descending { get; } = new ChatCompletionCollectionOrder(DescendingValue); + + public static bool operator ==(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right) => left.Equals(right); + + public static bool operator !=(ChatCompletionCollectionOrder left, ChatCompletionCollectionOrder right) => !left.Equals(right); + + public static implicit operator ChatCompletionCollectionOrder(string value) => new ChatCompletionCollectionOrder(value); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => obj is ChatCompletionCollectionOrder other && Equals(other); + + public bool Equals(ChatCompletionCollectionOrder other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => _value != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(_value) : 0; + + public override string ToString() => _value; + } +} diff --git a/src/Generated/Models/Chat/InternalChatCompletionList.Serialization.cs b/src/Generated/Models/Chat/InternalChatCompletionList.Serialization.cs index fe9235048..3871055d7 100644 --- a/src/Generated/Models/Chat/InternalChatCompletionList.Serialization.cs +++ b/src/Generated/Models/Chat/InternalChatCompletionList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalChatCompletionList PersistableModelCreateCore(BinaryDa } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalChatCompletionList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalChatCompletionList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Chat/InternalChatCompletionMessageList.Serialization.cs b/src/Generated/Models/Chat/InternalChatCompletionMessageList.Serialization.cs index 21bf965c2..4318e9032 100644 --- a/src/Generated/Models/Chat/InternalChatCompletionMessageList.Serialization.cs +++ b/src/Generated/Models/Chat/InternalChatCompletionMessageList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalChatCompletionMessageList PersistableModelCreateCore(B } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalChatCompletionMessageList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalChatCompletionMessageList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalDeleteEvalResponse.Serialization.cs b/src/Generated/Models/Evals/InternalDeleteEvalResponse.Serialization.cs index 074b57d44..bbffa3f0d 100644 --- a/src/Generated/Models/Evals/InternalDeleteEvalResponse.Serialization.cs +++ b/src/Generated/Models/Evals/InternalDeleteEvalResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -145,5 +146,12 @@ protected virtual InternalDeleteEvalResponse PersistableModelCreateCore(BinaryDa } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalDeleteEvalResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalDeleteEvalResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalDeleteEvalRunResponse.Serialization.cs b/src/Generated/Models/Evals/InternalDeleteEvalRunResponse.Serialization.cs index 4f0d872cc..86faf0d9f 100644 --- a/src/Generated/Models/Evals/InternalDeleteEvalRunResponse.Serialization.cs +++ b/src/Generated/Models/Evals/InternalDeleteEvalRunResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -145,5 +146,12 @@ protected virtual InternalDeleteEvalRunResponse PersistableModelCreateCore(Binar } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalDeleteEvalRunResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalDeleteEvalRunResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEval.Serialization.cs b/src/Generated/Models/Evals/InternalEval.Serialization.cs index f3c2c1a0f..f797abc57 100644 --- a/src/Generated/Models/Evals/InternalEval.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEval.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -254,5 +255,12 @@ protected virtual InternalEval PersistableModelCreateCore(BinaryData data, Model } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEval(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEval(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEvalList.Serialization.cs b/src/Generated/Models/Evals/InternalEvalList.Serialization.cs index 467bd927f..75418e07a 100644 --- a/src/Generated/Models/Evals/InternalEvalList.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEvalList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalEvalList PersistableModelCreateCore(BinaryData data, M } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEvalList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEvalList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEvalRun.Serialization.cs b/src/Generated/Models/Evals/InternalEvalRun.Serialization.cs index adff0abc6..46edc28d5 100644 --- a/src/Generated/Models/Evals/InternalEvalRun.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEvalRun.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -336,5 +337,12 @@ protected virtual InternalEvalRun PersistableModelCreateCore(BinaryData data, Mo } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEvalRun(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEvalRun(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEvalRunList.Serialization.cs b/src/Generated/Models/Evals/InternalEvalRunList.Serialization.cs index af0ce4eab..4bfce8682 100644 --- a/src/Generated/Models/Evals/InternalEvalRunList.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEvalRunList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalEvalRunList PersistableModelCreateCore(BinaryData data } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEvalRunList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEvalRunList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEvalRunOutputItem.Serialization.cs b/src/Generated/Models/Evals/InternalEvalRunOutputItem.Serialization.cs index 840ec8a0b..d2641487f 100644 --- a/src/Generated/Models/Evals/InternalEvalRunOutputItem.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEvalRunOutputItem.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -315,5 +316,12 @@ protected virtual InternalEvalRunOutputItem PersistableModelCreateCore(BinaryDat } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEvalRunOutputItem(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEvalRunOutputItem(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Evals/InternalEvalRunOutputItemList.Serialization.cs b/src/Generated/Models/Evals/InternalEvalRunOutputItemList.Serialization.cs index f008b5504..4ed0161b9 100644 --- a/src/Generated/Models/Evals/InternalEvalRunOutputItemList.Serialization.cs +++ b/src/Generated/Models/Evals/InternalEvalRunOutputItemList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalEvalRunOutputItemList PersistableModelCreateCore(Binar } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalEvalRunOutputItemList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalEvalRunOutputItemList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Files/InternalUpload.Serialization.cs b/src/Generated/Models/Files/InternalUpload.Serialization.cs index b793b213d..09c441ebc 100644 --- a/src/Generated/Models/Files/InternalUpload.Serialization.cs +++ b/src/Generated/Models/Files/InternalUpload.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -226,5 +227,12 @@ protected virtual InternalUpload PersistableModelCreateCore(BinaryData data, Mod } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalUpload(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalUpload(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Files/InternalUploadPart.Serialization.cs b/src/Generated/Models/Files/InternalUploadPart.Serialization.cs index 8883ad57b..cda4b7c95 100644 --- a/src/Generated/Models/Files/InternalUploadPart.Serialization.cs +++ b/src/Generated/Models/Files/InternalUploadPart.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -156,5 +157,12 @@ protected virtual InternalUploadPart PersistableModelCreateCore(BinaryData data, } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalUploadPart(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalUploadPart(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalDeleteFineTuningCheckpointPermissionResponse.Serialization.cs b/src/Generated/Models/FineTuning/InternalDeleteFineTuningCheckpointPermissionResponse.Serialization.cs index 74cc1decf..4988068a8 100644 --- a/src/Generated/Models/FineTuning/InternalDeleteFineTuningCheckpointPermissionResponse.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalDeleteFineTuningCheckpointPermissionResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -145,5 +146,12 @@ protected virtual InternalDeleteFineTuningCheckpointPermissionResponse Persistab } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalDeleteFineTuningCheckpointPermissionResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalDeleteFineTuningCheckpointPermissionResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalFineTuningJob.Serialization.cs b/src/Generated/Models/FineTuning/InternalFineTuningJob.Serialization.cs index b97c9b880..e2dddfa68 100644 --- a/src/Generated/Models/FineTuning/InternalFineTuningJob.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalFineTuningJob.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -498,5 +499,12 @@ protected virtual InternalFineTuningJob PersistableModelCreateCore(BinaryData da } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalFineTuningJob(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalFineTuningJob(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalListFineTuningCheckpointPermissionResponse.Serialization.cs b/src/Generated/Models/FineTuning/InternalListFineTuningCheckpointPermissionResponse.Serialization.cs index ba7dab38c..dd5c8274c 100644 --- a/src/Generated/Models/FineTuning/InternalListFineTuningCheckpointPermissionResponse.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalListFineTuningCheckpointPermissionResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -193,5 +194,12 @@ protected virtual InternalListFineTuningCheckpointPermissionResponse Persistable } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListFineTuningCheckpointPermissionResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListFineTuningCheckpointPermissionResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalListFineTuningJobCheckpointsResponse.Serialization.cs b/src/Generated/Models/FineTuning/InternalListFineTuningJobCheckpointsResponse.Serialization.cs index 035c35112..e5b209b77 100644 --- a/src/Generated/Models/FineTuning/InternalListFineTuningJobCheckpointsResponse.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalListFineTuningJobCheckpointsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -193,5 +194,12 @@ protected virtual InternalListFineTuningJobCheckpointsResponse PersistableModelC } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListFineTuningJobCheckpointsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListFineTuningJobCheckpointsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalListFineTuningJobEventsResponse.Serialization.cs b/src/Generated/Models/FineTuning/InternalListFineTuningJobEventsResponse.Serialization.cs index 68e008509..6f56e35f9 100644 --- a/src/Generated/Models/FineTuning/InternalListFineTuningJobEventsResponse.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalListFineTuningJobEventsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -155,5 +156,12 @@ protected virtual InternalListFineTuningJobEventsResponse PersistableModelCreate } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListFineTuningJobEventsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListFineTuningJobEventsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/FineTuning/InternalListPaginatedFineTuningJobsResponse.Serialization.cs b/src/Generated/Models/FineTuning/InternalListPaginatedFineTuningJobsResponse.Serialization.cs index 6e9277e4e..815408341 100644 --- a/src/Generated/Models/FineTuning/InternalListPaginatedFineTuningJobsResponse.Serialization.cs +++ b/src/Generated/Models/FineTuning/InternalListPaginatedFineTuningJobsResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -155,5 +156,12 @@ protected virtual InternalListPaginatedFineTuningJobsResponse PersistableModelCr } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListPaginatedFineTuningJobsResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListPaginatedFineTuningJobsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/LegacyCompletions/InternalCreateCompletionResponse.Serialization.cs b/src/Generated/Models/LegacyCompletions/InternalCreateCompletionResponse.Serialization.cs index 6c2ff891a..7538d6288 100644 --- a/src/Generated/Models/LegacyCompletions/InternalCreateCompletionResponse.Serialization.cs +++ b/src/Generated/Models/LegacyCompletions/InternalCreateCompletionResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -212,5 +213,12 @@ protected virtual InternalCreateCompletionResponse PersistableModelCreateCore(Bi } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalCreateCompletionResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalCreateCompletionResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Realtime/InternalRealtimeSessionCreateResponse.Serialization.cs b/src/Generated/Models/Realtime/InternalRealtimeSessionCreateResponse.Serialization.cs index ca291a979..cae237de8 100644 --- a/src/Generated/Models/Realtime/InternalRealtimeSessionCreateResponse.Serialization.cs +++ b/src/Generated/Models/Realtime/InternalRealtimeSessionCreateResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -360,5 +361,12 @@ protected virtual InternalRealtimeSessionCreateResponse PersistableModelCreateCo } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalRealtimeSessionCreateResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalRealtimeSessionCreateResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Realtime/InternalRealtimeTranscriptionSessionCreateResponse.Serialization.cs b/src/Generated/Models/Realtime/InternalRealtimeTranscriptionSessionCreateResponse.Serialization.cs index ede2199db..ebb6c1a31 100644 --- a/src/Generated/Models/Realtime/InternalRealtimeTranscriptionSessionCreateResponse.Serialization.cs +++ b/src/Generated/Models/Realtime/InternalRealtimeTranscriptionSessionCreateResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -195,5 +196,12 @@ protected virtual InternalRealtimeTranscriptionSessionCreateResponse Persistable } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalRealtimeTranscriptionSessionCreateResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalRealtimeTranscriptionSessionCreateResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/Responses/InternalResponseItemList.Serialization.cs b/src/Generated/Models/Responses/InternalResponseItemList.Serialization.cs index 288436848..fd000f146 100644 --- a/src/Generated/Models/Responses/InternalResponseItemList.Serialization.cs +++ b/src/Generated/Models/Responses/InternalResponseItemList.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -183,5 +184,12 @@ protected virtual InternalResponseItemList PersistableModelCreateCore(BinaryData } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalResponseItemList(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalResponseItemList(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/VectorStores/InternalListVectorStoreFilesResponse.Serialization.cs b/src/Generated/Models/VectorStores/InternalListVectorStoreFilesResponse.Serialization.cs index 3f16044ee..02b61833d 100644 --- a/src/Generated/Models/VectorStores/InternalListVectorStoreFilesResponse.Serialization.cs +++ b/src/Generated/Models/VectorStores/InternalListVectorStoreFilesResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListVectorStoreFilesResponse PersistableModelCreateCor } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListVectorStoreFilesResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListVectorStoreFilesResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/VectorStores/InternalListVectorStoresResponse.Serialization.cs b/src/Generated/Models/VectorStores/InternalListVectorStoresResponse.Serialization.cs index 5e5a3f70e..c4442b048 100644 --- a/src/Generated/Models/VectorStores/InternalListVectorStoresResponse.Serialization.cs +++ b/src/Generated/Models/VectorStores/InternalListVectorStoresResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -184,5 +185,12 @@ protected virtual InternalListVectorStoresResponse PersistableModelCreateCore(Bi } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalListVectorStoresResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalListVectorStoresResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/VectorStores/InternalVectorStoreFileContentResponse.Serialization.cs b/src/Generated/Models/VectorStores/InternalVectorStoreFileContentResponse.Serialization.cs index 2b9dab0d8..8cc2e1074 100644 --- a/src/Generated/Models/VectorStores/InternalVectorStoreFileContentResponse.Serialization.cs +++ b/src/Generated/Models/VectorStores/InternalVectorStoreFileContentResponse.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -178,5 +179,12 @@ protected virtual InternalVectorStoreFileContentResponse PersistableModelCreateC } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalVectorStoreFileContentResponse(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalVectorStoreFileContentResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/Models/VectorStores/InternalVectorStoreSearchResultsPage.Serialization.cs b/src/Generated/Models/VectorStores/InternalVectorStoreSearchResultsPage.Serialization.cs index cab34a719..0f4363bcb 100644 --- a/src/Generated/Models/VectorStores/InternalVectorStoreSearchResultsPage.Serialization.cs +++ b/src/Generated/Models/VectorStores/InternalVectorStoreSearchResultsPage.Serialization.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Text.Json; @@ -217,5 +218,12 @@ protected virtual InternalVectorStoreSearchResultsPage PersistableModelCreateCor } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static explicit operator InternalVectorStoreSearchResultsPage(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeInternalVectorStoreSearchResultsPage(document.RootElement, ModelSerializationExtensions.WireOptions); + } } } diff --git a/src/Generated/OpenAIModelFactory.cs b/src/Generated/OpenAIModelFactory.cs index abbb173c5..72c0f256b 100644 --- a/src/Generated/OpenAIModelFactory.cs +++ b/src/Generated/OpenAIModelFactory.cs @@ -1070,6 +1070,19 @@ public static AudioTranscription AudioTranscription(string language = default, s additionalBinaryDataProperties: null); } + public static ChatCompletionCollectionOptions ChatCompletionCollectionOptions(string afterId = default, int? pageSizeLimit = default, ChatCompletionCollectionOrder? order = default, IDictionary metadata = default, string model = default) + { + metadata ??= new ChangeTrackingDictionary(); + + return new ChatCompletionCollectionOptions( + afterId, + pageSizeLimit, + order, + metadata, + model, + additionalBinaryDataProperties: null); + } + public static AudioTokenLogProbabilityDetails AudioTokenLogProbabilityDetails(string token = default, float logProbability = default, ReadOnlyMemory utf8Bytes = default) { return new AudioTokenLogProbabilityDetails(token, logProbability, utf8Bytes, additionalBinaryDataProperties: null); diff --git a/tests/Chat/ChatTests.cs b/tests/Chat/ChatTests.cs index a6a003a67..6b63c4fea 100644 --- a/tests/Chat/ChatTests.cs +++ b/tests/Chat/ChatTests.cs @@ -1042,6 +1042,13 @@ public async Task ChatMetadata() ChatCompletion completion = await client.CompleteChatAsync( ["Hello, world!"], options); + + int count = 0; + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync()) + { + count++; + } + Assert.That(count, Is.GreaterThan(0)); } [Test] @@ -1061,6 +1068,392 @@ public async Task WebSearchWorks() Assert.That(completion.Annotations, Has.Count.GreaterThan(0)); } + [Test] + public async Task GetChatCompletionsWithPagination() + { + ChatClient client = GetTestClient(); + + // Create multiple completions with stored output enabled + var completionIds = new List(); + for (int i = 0; i < 3; i++) + { + ChatCompletionOptions options = new() + { + StoredOutputEnabled = true, + Metadata = { ["test_key"] = $"test_value_{i}" } + }; + + ChatCompletion completion = await client.CompleteChatAsync( + [$"Test message {i}: Say 'Hello World {i}'"], + options); + + completionIds.Add(completion.Id); + } + + Thread.Sleep(5000); // Wait for completions to be stored + + // Test pagination with limit + ChatCompletionCollectionOptions paginationOptions = new() + { + PageSizeLimit = 2 + }; + + int totalCount = 0; + string lastId = null; + + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(paginationOptions)) + { + totalCount++; + lastId = fetchedCompletion.Id; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + Assert.That(fetchedCompletion.Content, Is.Not.Null); + + if (totalCount >= 2) break; // Stop after getting 2 items + } + + Assert.That(totalCount, Is.EqualTo(2)); + Assert.That(lastId, Is.Not.Null); + + // Clean up + foreach (var id in completionIds) + { + try + { + await client.DeleteChatCompletionAsync(id); + } + catch { /* Ignore cleanup errors */ } + } + } + + [Test] + public async Task GetChatCompletionsWithAfterIdPagination() + { + ChatClient client = GetTestClient(); + + // Create multiple completions + var completionIds = new List(); + for (int i = 0; i < 3; i++) + { + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true + }; + + ChatCompletion completion = await client.CompleteChatAsync( + [$"Pagination test {i}: Say 'Test {i}'"], + createOptions); + + completionIds.Add(completion.Id); + } + + Thread.Sleep(5000); // Wait for completions to be stored + + // Get first completion to use as afterId + string afterId = null; + await foreach (var firstCompletion in client.GetChatCompletionsAsync()) + { + afterId = firstCompletion.Id; + break; + } + + Assert.That(afterId, Is.Not.Null); + + // Test pagination starting after the first ID + ChatCompletionCollectionOptions paginationOptions = new() + { + AfterId = afterId, + PageSizeLimit = 2 + }; + + int count = 0; + await foreach (var completion in client.GetChatCompletionsAsync(paginationOptions)) + { + count++; + // Ensure we don't get the afterId completion + Assert.That(completion.Id, Is.Not.EqualTo(afterId)); + if (count >= 2) break; + } + + // Clean up + foreach (var id in completionIds) + { + try + { + await client.DeleteChatCompletionAsync(id); + } + catch { /* Ignore cleanup errors */ } + } + } + + [Test] + public async Task GetChatCompletionsWithOrderFiltering() + { + ChatClient client = GetTestClient(); + + // Create completions with timestamps + var completionIds = new List(); + for (int i = 0; i < 2; i++) + { + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true, + Metadata = { ["sequence"] = i.ToString() } + }; + + ChatCompletion completion = await client.CompleteChatAsync( + [$"Order test {i}: Say 'Order {i}'"], + createOptions); + + completionIds.Add(completion.Id); + Thread.Sleep(1000); // Ensure different timestamps + } + + Thread.Sleep(5000); // Wait for completions to be stored + + // Test ascending order + ChatCompletionCollectionOptions ascOptions = new() + { + Order = ChatCompletionCollectionOrder.Ascending, + PageSizeLimit = 5 + }; + + var ascResults = new List(); + await foreach (var completion in client.GetChatCompletionsAsync(ascOptions)) + { + ascResults.Add(completion); + if (ascResults.Count >= 2) break; + } + + // Test descending order + ChatCompletionCollectionOptions descOptions = new() + { + Order = ChatCompletionCollectionOrder.Descending, + PageSizeLimit = 5 + }; + + var descResults = new List(); + await foreach (var completion in client.GetChatCompletionsAsync(descOptions)) + { + descResults.Add(completion); + if (descResults.Count >= 2) break; + } + + // Verify we get results in both cases + Assert.That(ascResults, Has.Count.GreaterThan(0)); + Assert.That(descResults, Has.Count.GreaterThan(0)); + + // The first result in descending order should be the most recent + // (though exact ordering validation is tricky due to timing) + Assert.That(descResults[0].Id, Is.Not.Null.And.Not.Empty); + + // Clean up + foreach (var id in completionIds) + { + try + { + await client.DeleteChatCompletionAsync(id); + } + catch { /* Ignore cleanup errors */ } + } + } + + [Test] + public async Task GetChatCompletionsWithMetadataFiltering() + { + ChatClient client = GetTestClient(); + + // Create completions with different metadata + var testMetadataKey = $"test_scenario_{Guid.NewGuid():N}"; + var completionIds = new List(); + + // Create completion with specific metadata + ChatCompletionOptions options1 = new() + { + StoredOutputEnabled = true, + Metadata = { [testMetadataKey] = "target_value" } + }; + + ChatCompletion targetCompletion = await client.CompleteChatAsync( + ["Metadata test: Say 'Target completion'"], + options1); + completionIds.Add(targetCompletion.Id); + + // Create completion with different metadata + ChatCompletionOptions options2 = new() + { + StoredOutputEnabled = true, + Metadata = { [testMetadataKey] = "other_value" } + }; + + ChatCompletion otherCompletion = await client.CompleteChatAsync( + ["Metadata test: Say 'Other completion'"], + options2); + completionIds.Add(otherCompletion.Id); + + Thread.Sleep(5000); // Wait for completions to be stored + + // Filter by specific metadata + ChatCompletionCollectionOptions filterOptions = new() + { + Metadata = { [testMetadataKey] = "target_value" }, + PageSizeLimit = 10 + }; + + int totalFound = 0; + + await foreach (var completion in client.GetChatCompletionsAsync(filterOptions)) + { + totalFound++; + Assert.That(completion.Id, Is.Not.Null.And.Not.Empty); + if (totalFound >= 20) break; // Prevent infinite loop + } + + // Should find completions (filtering behavior may vary by implementation) + Assert.That(totalFound, Is.GreaterThan(0)); + + // Clean up + foreach (var id in completionIds) + { + try + { + await client.DeleteChatCompletionAsync(id); + } + catch { /* Ignore cleanup errors */ } + } + } + + [Test] + public async Task GetChatCompletionsWithModelFiltering() + { + ChatClient client = GetTestClient(); + + // Create completion with default model + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true, + Metadata = { ["model_test"] = "true" } + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Model filter test: Say 'Hello'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Filter by the model used by the test client + ChatCompletionCollectionOptions filterOptions = new() + { + Model = "gpt-4o-mini-2024-07-18", // Common test model + PageSizeLimit = 10 + }; + + int count = 0; + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(filterOptions)) + { + count++; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + Assert.That(fetchedCompletion.Model, Is.EqualTo(filterOptions.Model)); + if (count >= 5) break; // Limit results for test performance + } + + Assert.That(count, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + + [Test] + public async Task GetChatCompletionsWithEmptyOptions() + { + ChatClient client = GetTestClient(); + + // Create a completion to ensure we have something to fetch + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Empty options test: Say 'Hello'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Test with default/empty options + int count = 0; + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync()) + { + count++; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + Assert.That(fetchedCompletion.Content, Is.Not.Null); + if (count >= 3) break; // Limit for test performance + } + + Assert.That(count, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + + [Test] + public async Task GetChatCompletionsWithCombinedFilters() + { + ChatClient client = GetTestClient(); + + // Create completion with combined metadata for filtering + var testKey = $"combined_test_{Guid.NewGuid():N}"; + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true, + Metadata = + { + [testKey] = "combined_value", + ["test_type"] = "integration" + } + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Combined filters test: Say 'Combined test'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Test with combined filters + ChatCompletionCollectionOptions combinedOptions = new() + { + PageSizeLimit = 5, + Order = ChatCompletionCollectionOrder.Descending, + Metadata = { [testKey] = "combined_value" } + }; + + int count = 0; + + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(combinedOptions)) + { + count++; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + + if (count >= 10) break; // Prevent excessive iterations + } + + Assert.That(count, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + [Test] public async Task FileIdContentWorks() { @@ -1162,6 +1555,143 @@ private void Validate(T item) } } + [Test] + public async Task GetChatCompletionsValidatesCollectionEnumeration() + { + ChatClient client = GetTestClient(); + + // Create a completion to ensure we have data + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true, + Metadata = { ["enumeration_test"] = "true" } + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Enumeration test: Say 'Test enumeration'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Test that we can enumerate multiple times + ChatCompletionCollectionOptions collectionOptions = new() + { + PageSizeLimit = 2 + }; + + var collection = client.GetChatCompletionsAsync(collectionOptions); + + // First enumeration + int firstCount = 0; + await foreach (var item in collection) + { + firstCount++; + Assert.That(item.Id, Is.Not.Null.And.Not.Empty); + if (firstCount >= 2) break; + } + + // Second enumeration (should work independently) + int secondCount = 0; + await foreach (var item in collection) + { + secondCount++; + Assert.That(item.Id, Is.Not.Null.And.Not.Empty); + if (secondCount >= 2) break; + } + + Assert.That(firstCount, Is.GreaterThan(0)); + Assert.That(secondCount, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + + [Test] + public async Task GetChatCompletionsHandlesLargeLimits() + { + ChatClient client = GetTestClient(); + + // Create a completion for testing + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Large limit test: Say 'Testing large limits'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Test with a large page size limit + ChatCompletionCollectionOptions largeOptions = new() + { + PageSizeLimit = 100 + }; + + int count = 0; + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(largeOptions)) + { + count++; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + if (count >= 20) break; // Prevent excessive test time + } + + Assert.That(count, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + + [Test] + public async Task GetChatCompletionsWithMinimalLimits() + { + ChatClient client = GetTestClient(); + + // Create a completion for testing + ChatCompletionOptions createOptions = new() + { + StoredOutputEnabled = true + }; + + ChatCompletion completion = await client.CompleteChatAsync( + ["Minimal limit test: Say 'Testing minimal limits'"], + createOptions); + + Thread.Sleep(5000); // Wait for completion to be stored + + // Test with minimal page size + ChatCompletionCollectionOptions minimalOptions = new() + { + PageSizeLimit = 1 + }; + + int count = 0; + await foreach (var fetchedCompletion in client.GetChatCompletionsAsync(minimalOptions)) + { + count++; + Assert.That(fetchedCompletion.Id, Is.Not.Null.And.Not.Empty); + if (count >= 3) break; // Get a few items to verify pagination works + } + + Assert.That(count, Is.GreaterThan(0)); + + // Clean up + try + { + await client.DeleteChatCompletionAsync(completion.Id); + } + catch { /* Ignore cleanup errors */ } + } + [OneTimeTearDown] public void TearDown() {