openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.0.0-beta.10

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/Utility/MultipartFormDataBinaryContent.cs

170lines · modecode

1using System;
2using System.ClientModel;
3using System.Diagnostics;
4using System.Globalization;
5using System.IO;
6using System.Net.Http;
7using System.Net.Http.Headers;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace OpenAI;
12
13internal class MultipartFormDataBinaryContent : BinaryContent
14{
15 private readonly MultipartFormDataContent _multipartContent;
16
17 private const int BoundaryLength = 70;
18 private const string BoundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
19
20 public MultipartFormDataBinaryContent()
21 {
22 _multipartContent = new MultipartFormDataContent(CreateBoundary());
23 }
24
25 public string ContentType
26 {
27 get
28 {
29 Debug.Assert(_multipartContent.Headers.ContentType is not null);
30
31 return _multipartContent.Headers.ContentType!.ToString();
32 }
33 }
34
35 internal HttpContent HttpContent => _multipartContent;
36
37 public void Add(Stream stream, string name, string fileName = default, string contentType = null)
38 {
39 Argument.AssertNotNull(stream, nameof(stream));
40
41 StreamContent content = new(stream);
42 if (contentType is not null)
43 {
44 content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
45 }
46 Add(content, name, fileName);
47 }
48
49 public void Add(string content, string name, string fileName = default)
50 {
51 Add(new StringContent(content), name, fileName);
52 }
53
54 public void Add(int content, string name, string fileName = default)
55 {
56 // https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString
57 string value = content.ToString("G", CultureInfo.InvariantCulture);
58 Add(new StringContent(value), name, fileName);
59 }
60
61 public void Add(double content, string name, string fileName = default)
62 {
63 // https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString
64 string value = content.ToString("G", CultureInfo.InvariantCulture);
65 Add(new StringContent(value), name, fileName);
66 }
67
68 public void Add(byte[] content, string name, string fileName = default)
69 {
70 Add(new ByteArrayContent(content), name, fileName);
71 }
72
73 public void Add(BinaryData content, string name, string fileName = default)
74 {
75 Add(new ByteArrayContent(content.ToArray()), name, fileName);
76 }
77
78 private void Add(HttpContent content, string name, string fileName)
79 {
80 Argument.AssertNotNull(content, nameof(content));
81 Argument.AssertNotNull(name, nameof(name));
82
83 if (fileName is not null)
84 {
85 _multipartContent.Add(content, name, fileName);
86 }
87 else
88 {
89 _multipartContent.Add(content, name);
90 }
91 }
92
93#if NET6_0_OR_GREATER
94 private static string CreateBoundary() =>
95 string.Create(BoundaryLength, 0, (chars, _) =>
96 {
97 Span<byte> random = stackalloc byte[BoundaryLength];
98 Random.Shared.NextBytes(random);
99
100 for (int i = 0; i < chars.Length; i++)
101 {
102 chars[i] = BoundaryValues[random[i] % BoundaryValues.Length];
103 }
104 });
105#else
106 private static readonly Random _random = new();
107
108 private static string CreateBoundary()
109 {
110 Span<char> chars = stackalloc char[BoundaryLength];
111
112 byte[] random = new byte[BoundaryLength];
113 lock (_random)
114 {
115 _random.NextBytes(random);
116 }
117
118 // Instead of `% BoundaryValues.Length` as is used above, use a mask to achieve the same result.
119 // `% BoundaryValues.Length` is optimized to the equivalent on .NET Core but not on .NET Framework.
120 const int Mask = 255 >> 2;
121 Debug.Assert(BoundaryValues.Length - 1 == Mask);
122
123 for (int i = 0; i < chars.Length; i++)
124 {
125 chars[i] = BoundaryValues[random[i] & Mask];
126 }
127
128 return chars.ToString();
129 }
130#endif
131
132 public override bool TryComputeLength(out long length)
133 {
134 // We can't call the protected method on HttpContent
135
136 if (_multipartContent.Headers.ContentLength is long contentLength)
137 {
138 length = contentLength;
139 return true;
140 }
141
142 length = 0;
143 return false;
144 }
145
146 public override void WriteTo(Stream stream, CancellationToken cancellationToken = default)
147 {
148#if NET5_0_OR_GREATER
149 _multipartContent.CopyTo(stream, default, cancellationToken);
150#else
151 // TODO: polyfill sync-over-async for netstandard2.0 for Azure clients.
152 // Tracked by https://github.com/Azure/azure-sdk-for-net/issues/42674
153 _multipartContent.CopyToAsync(stream).GetAwaiter().GetResult();
154#endif
155 }
156
157 public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default)
158 {
159#if NET5_0_OR_GREATER
160 await _multipartContent.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
161#else
162 await _multipartContent.CopyToAsync(stream).ConfigureAwait(false);
163#endif
164 }
165
166 public override void Dispose()
167 {
168 _multipartContent.Dispose();
169 }
170}
171