@@ -26,21 +26,21 @@ public sealed partial class SessionsPythonPlugin
26
26
private const string ApiVersion = "2024-10-02-preview" ;
27
27
private readonly Uri _poolManagementEndpoint ;
28
28
private readonly SessionsPythonSettings _settings ;
29
- private readonly Func < Task < string > > ? _authTokenProvider ;
29
+ private readonly Func < CancellationToken , Task < string > > ? _authTokenProvider ;
30
30
private readonly IHttpClientFactory _httpClientFactory ;
31
31
private readonly ILogger _logger ;
32
32
33
33
/// <summary>
34
34
/// Initializes a new instance of the SessionsPythonTool class.
35
35
/// </summary>
36
- /// <param name="settings">The settings for the Python tool plugin. </param>
37
- /// <param name="httpClientFactory">The HTTP client factory. </param>
38
- /// <param name="authTokenProvider"> Optional provider for auth token generation. </param>
39
- /// <param name="loggerFactory">The logger factory. </param>
36
+ /// <param name="settings">The settings for the Python tool plugin.</param>
37
+ /// <param name="httpClientFactory">The HTTP client factory.</param>
38
+ /// <param name="authTokenProvider">Optional provider for auth token generation.</param>
39
+ /// <param name="loggerFactory">The logger factory.</param>
40
40
public SessionsPythonPlugin (
41
41
SessionsPythonSettings settings ,
42
42
IHttpClientFactory httpClientFactory ,
43
- Func < Task < string > > ? authTokenProvider = null ,
43
+ Func < CancellationToken , Task < string > > ? authTokenProvider = null ,
44
44
ILoggerFactory ? loggerFactory = null )
45
45
{
46
46
Verify . NotNull ( settings , nameof ( settings ) ) ;
@@ -66,7 +66,8 @@ public SessionsPythonPlugin(
66
66
/// Keep everything in a single line; the \n sequences will represent line breaks
67
67
/// when the string is processed or displayed.
68
68
/// </summary>
69
- /// <param name="code"> The valid Python code to execute. </param>
69
+ /// <param name="code"> The valid Python code to execute.</param>
70
+ /// <param name="cancellationToken">The cancellation token.</param>
70
71
/// <returns> The result of the Python code execution. </returns>
71
72
/// <exception cref="ArgumentNullException"></exception>
72
73
/// <exception cref="HttpRequestException"></exception>
@@ -79,7 +80,9 @@ Add spaces directly after \n sequences to replicate indentation.
79
80
Keep everything in a single line; the \n sequences will represent line breaks
80
81
when the string is processed or displayed.
81
82
""" ) ]
82
- public async Task < string > ExecuteCodeAsync ( [ Description ( "The valid Python code to execute." ) ] string code )
83
+ public async Task < string > ExecuteCodeAsync (
84
+ [ Description ( "The valid Python code to execute." ) ] string code ,
85
+ CancellationToken cancellationToken = default )
83
86
{
84
87
Verify . NotNullOrWhiteSpace ( code , nameof ( code ) ) ;
85
88
@@ -91,15 +94,14 @@ public async Task<string> ExecuteCodeAsync([Description("The valid Python code t
91
94
this . _logger . LogTrace ( "Executing Python code: {Code}" , code ) ;
92
95
93
96
using var httpClient = this . _httpClientFactory . CreateClient ( ) ;
94
- await this . AddHeadersAsync ( httpClient ) . ConfigureAwait ( false ) ;
95
97
96
98
var requestBody = new SessionsPythonCodeExecutionProperties ( this . _settings , code ) ;
97
99
98
100
using var content = new StringContent ( JsonSerializer . Serialize ( requestBody ) , Encoding . UTF8 , "application/json" ) ;
99
101
100
- using var response = await this . SendAsync ( httpClient , HttpMethod . Post , "executions" , content ) . ConfigureAwait ( false ) ;
102
+ using var response = await this . SendAsync ( httpClient , HttpMethod . Post , "executions" , cancellationToken , content ) . ConfigureAwait ( false ) ;
101
103
102
- var responseContent = JsonSerializer . Deserialize < JsonElement > ( await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ) ;
104
+ var responseContent = JsonSerializer . Deserialize < JsonElement > ( await response . Content . ReadAsStringWithExceptionMappingAsync ( cancellationToken ) . ConfigureAwait ( false ) ) ;
103
105
104
106
var result = responseContent . GetProperty ( "result" ) ;
105
107
@@ -120,21 +122,22 @@ public async Task<string> ExecuteCodeAsync([Description("The valid Python code t
120
122
/// </summary>
121
123
/// <param name="remoteFileName">The name of the remote file, relative to `/mnt/data`.</param>
122
124
/// <param name="localFilePath">The path to the file on the local machine.</param>
125
+ /// <param name="cancellationToken">The cancellation token.</param>
123
126
/// <returns>The metadata of the uploaded file.</returns>
124
127
/// <exception cref="ArgumentNullException"></exception>
125
128
/// <exception cref="HttpRequestException"></exception>
126
129
[ KernelFunction , Description ( "Uploads a file to the `/mnt/data` directory of the current session." ) ]
127
130
public async Task < SessionsRemoteFileMetadata > UploadFileAsync (
128
131
[ Description ( "The name of the remote file, relative to `/mnt/data`." ) ] string remoteFileName ,
129
- [ Description ( "The path to the file on the local machine." ) ] string localFilePath )
132
+ [ Description ( "The path to the file on the local machine." ) ] string localFilePath ,
133
+ CancellationToken cancellationToken = default )
130
134
{
131
135
Verify . NotNullOrWhiteSpace ( remoteFileName , nameof ( remoteFileName ) ) ;
132
136
Verify . NotNullOrWhiteSpace ( localFilePath , nameof ( localFilePath ) ) ;
133
137
134
138
this . _logger . LogInformation ( "Uploading file: {LocalFilePath} to {RemoteFileName}" , localFilePath , remoteFileName ) ;
135
139
136
140
using var httpClient = this . _httpClientFactory . CreateClient ( ) ;
137
- await this . AddHeadersAsync ( httpClient ) . ConfigureAwait ( false ) ;
138
141
139
142
using var fileContent = new ByteArrayContent ( File . ReadAllBytes ( localFilePath ) ) ;
140
143
@@ -143,9 +146,9 @@ public async Task<SessionsRemoteFileMetadata> UploadFileAsync(
143
146
{ fileContent , "file" , remoteFileName } ,
144
147
} ;
145
148
146
- using var response = await this . SendAsync ( httpClient , HttpMethod . Post , "files" , multipartFormDataContent ) . ConfigureAwait ( false ) ;
149
+ using var response = await this . SendAsync ( httpClient , HttpMethod . Post , "files" , cancellationToken , multipartFormDataContent ) . ConfigureAwait ( false ) ;
147
150
148
- var stringContent = await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ;
151
+ var stringContent = await response . Content . ReadAsStringWithExceptionMappingAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
149
152
150
153
return JsonSerializer . Deserialize < SessionsRemoteFileMetadata > ( stringContent ) ! ;
151
154
}
@@ -155,22 +158,23 @@ public async Task<SessionsRemoteFileMetadata> UploadFileAsync(
155
158
/// </summary>
156
159
/// <param name="remoteFileName">The name of the remote file to download, relative to `/mnt/data`.</param>
157
160
/// <param name="localFilePath">The path to save the downloaded file to. If not provided won't save it in the disk.</param>
161
+ /// <param name="cancellationToken">The cancellation token.</param>
158
162
/// <returns>The data of the downloaded file as byte array.</returns>
159
163
[ KernelFunction , Description ( "Downloads a file from the `/mnt/data` directory of the current session." ) ]
160
164
public async Task < byte [ ] > DownloadFileAsync (
161
165
[ Description ( "The name of the remote file to download, relative to `/mnt/data`." ) ] string remoteFileName ,
162
- [ Description ( "The path to save the downloaded file to. If not provided won't save it in the disk." ) ] string ? localFilePath = null )
166
+ [ Description ( "The path to save the downloaded file to. If not provided won't save it in the disk." ) ] string ? localFilePath = null ,
167
+ CancellationToken cancellationToken = default )
163
168
{
164
169
Verify . NotNullOrWhiteSpace ( remoteFileName , nameof ( remoteFileName ) ) ;
165
170
166
171
this . _logger . LogTrace ( "Downloading file: {RemoteFileName} to {LocalFileName}" , remoteFileName , localFilePath ) ;
167
172
168
173
using var httpClient = this . _httpClientFactory . CreateClient ( ) ;
169
- await this . AddHeadersAsync ( httpClient ) . ConfigureAwait ( false ) ;
170
174
171
- using var response = await this . SendAsync ( httpClient , HttpMethod . Get , $ "files/{ Uri . EscapeDataString ( remoteFileName ) } /content") . ConfigureAwait ( false ) ;
175
+ using var response = await this . SendAsync ( httpClient , HttpMethod . Get , $ "files/{ Uri . EscapeDataString ( remoteFileName ) } /content", cancellationToken ) . ConfigureAwait ( false ) ;
172
176
173
- var fileContent = await response . Content . ReadAsByteArrayAsync ( ) . ConfigureAwait ( false ) ;
177
+ var fileContent = await response . Content . ReadAsByteArrayAndTranslateExceptionAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
174
178
175
179
if ( ! string . IsNullOrWhiteSpace ( localFilePath ) )
176
180
{
@@ -190,18 +194,18 @@ public async Task<byte[]> DownloadFileAsync(
190
194
/// <summary>
191
195
/// Lists all entities: files or directories in the `/mnt/data` directory of the current session.
192
196
/// </summary>
197
+ /// <param name="cancellationToken">The cancellation token.</param>
193
198
/// <returns>The list of files in the session.</returns>
194
199
[ KernelFunction , Description ( "Lists all entities: files or directories in the `/mnt/data` directory of the current session." ) ]
195
- public async Task < IReadOnlyList < SessionsRemoteFileMetadata > > ListFilesAsync ( )
200
+ public async Task < IReadOnlyList < SessionsRemoteFileMetadata > > ListFilesAsync ( CancellationToken cancellationToken = default )
196
201
{
197
202
this . _logger . LogTrace ( "Listing files for Session ID: {SessionId}" , this . _settings . SessionId ) ;
198
203
199
204
using var httpClient = this . _httpClientFactory . CreateClient ( ) ;
200
- await this . AddHeadersAsync ( httpClient ) . ConfigureAwait ( false ) ;
201
205
202
- using var response = await this . SendAsync ( httpClient , HttpMethod . Get , "files" ) . ConfigureAwait ( false ) ;
206
+ using var response = await this . SendAsync ( httpClient , HttpMethod . Get , "files" , cancellationToken ) . ConfigureAwait ( false ) ;
203
207
204
- var jsonElementResult = JsonSerializer . Deserialize < JsonElement > ( await response . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ) ;
208
+ var jsonElementResult = JsonSerializer . Deserialize < JsonElement > ( await response . Content . ReadAsStringWithExceptionMappingAsync ( cancellationToken ) . ConfigureAwait ( false ) ) ;
205
209
206
210
var files = jsonElementResult . GetProperty ( "value" ) ;
207
211
@@ -241,16 +245,17 @@ private static string SanitizeCodeInput(string code)
241
245
}
242
246
243
247
/// <summary>
244
- /// Add headers to the HTTP client .
248
+ /// Add headers to the HTTP request .
245
249
/// </summary>
246
- /// <param name="httpClient">The HTTP client to add headers to.</param>
247
- private async Task AddHeadersAsync ( HttpClient httpClient )
250
+ /// <param name="request">The HTTP request to add headers to.</param>
251
+ /// <param name="cancellationToken">The cancellation token.</param>
252
+ private async Task AddHeadersAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
248
253
{
249
- httpClient . DefaultRequestHeaders . Add ( "User-Agent" , $ "{ HttpHeaderConstant . Values . UserAgent } /{ s_assemblyVersion } (Language=dotnet)") ;
254
+ request . Headers . Add ( "User-Agent" , $ "{ HttpHeaderConstant . Values . UserAgent } /{ s_assemblyVersion } (Language=dotnet)") ;
250
255
251
256
if ( this . _authTokenProvider is not null )
252
257
{
253
- httpClient . DefaultRequestHeaders . Add ( "Authorization" , $ "Bearer { ( await this . _authTokenProvider ( ) . ConfigureAwait ( false ) ) } ") ;
258
+ request . Headers . Add ( "Authorization" , $ "Bearer { ( await this . _authTokenProvider ( cancellationToken ) . ConfigureAwait ( false ) ) } ") ;
254
259
}
255
260
}
256
261
@@ -260,9 +265,10 @@ private async Task AddHeadersAsync(HttpClient httpClient)
260
265
/// <param name="httpClient">The HTTP client to use.</param>
261
266
/// <param name="method">The HTTP method to use.</param>
262
267
/// <param name="path">The path to send the request to.</param>
268
+ /// <param name="cancellationToken">The cancellation token.</param>
263
269
/// <param name="httpContent">The content to send with the request.</param>
264
270
/// <returns>The HTTP response message.</returns>
265
- private async Task < HttpResponseMessage > SendAsync ( HttpClient httpClient , HttpMethod method , string path , HttpContent ? httpContent = null )
271
+ private async Task < HttpResponseMessage > SendAsync ( HttpClient httpClient , HttpMethod method , string path , CancellationToken cancellationToken , HttpContent ? httpContent = null )
266
272
{
267
273
// The query string is the same for all operations
268
274
var pathWithQueryString = $ "{ path } ?identifier={ this . _settings . SessionId } &api-version={ ApiVersion } ";
@@ -281,7 +287,9 @@ private async Task<HttpResponseMessage> SendAsync(HttpClient httpClient, HttpMet
281
287
Content = httpContent ,
282
288
} ;
283
289
284
- return await httpClient . SendWithSuccessCheckAsync ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
290
+ await this . AddHeadersAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
291
+
292
+ return await httpClient . SendWithSuccessCheckAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
285
293
}
286
294
287
295
#if NET
0 commit comments