diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
index 0ca0c54d3..3f550faa6 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Coding/ICodeProcessor.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.Coding.Models;
using BotSharp.Abstraction.Coding.Options;
using BotSharp.Abstraction.Coding.Responses;
+using System.Threading;
namespace BotSharp.Abstraction.Coding;
@@ -11,11 +12,12 @@ public interface ICodeProcessor
///
/// Run code script
///
- /// The code scirpt to run
+ /// The code script to run
/// Code script execution options
+ /// The cancellation token
///
///
- Task RunAsync(string codeScript, CodeInterpretOptions? options = null)
+ Task RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeInterpretOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeInterpretOptions.cs
index 5dec8367e..b392d6351 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeInterpretOptions.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Options/CodeInterpretOptions.cs
@@ -6,7 +6,6 @@ public class CodeInterpretOptions
{
public string? ScriptName { get; set; }
public IEnumerable? Arguments { get; set; }
- public bool UseMutex { get; set; }
+ public bool UseLock { get; set; }
public bool UseProcess { get; set; }
- public CancellationToken? CancellationToken { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs
index 2e46e3857..405028699 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Coding/Responses/CodeInterpretResponse.cs
@@ -6,6 +6,6 @@ public class CodeInterpretResponse : ResponseBase
public override string ToString()
{
- return Result ?? ErrorMsg ?? $"Success: {Success}";
+ return Result.IfNullOrEmptyAs(ErrorMsg ?? $"Success: {Success}")!;
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs
index 182196753..25f63949c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Options/CodeInstructOptions.cs
@@ -29,4 +29,11 @@ public class CodeInstructOptions
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("arguments")]
public List? Arguments { get; set; }
+
+ ///
+ /// Timeout in seconds
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("timeout_seconds")]
+ public int? TimeoutSeconds { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
index 00fd02b6d..535547005 100644
--- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
+++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs
@@ -254,13 +254,13 @@ await hook.OnResponseGenerated(new InstructResponseModel
}
// Run code script
- using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
+ var seconds = codeOptions?.TimeoutSeconds > 0 ? codeOptions.TimeoutSeconds.Value : 3;
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(seconds));
var codeResponse = await codeProcessor.RunAsync(context.CodeScript, options: new()
{
ScriptName = scriptName,
- Arguments = context.Arguments,
- CancellationToken = cts.Token
- });
+ Arguments = context.Arguments
+ }, cancellationToken: cts.Token);
if (codeResponse == null || !codeResponse.Success)
{
diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
index 107644742..212d8c0cb 100644
--- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
+++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyCodeInterpreter.cs
@@ -30,17 +30,17 @@ public PyCodeInterpreter(
public string Provider => "botsharp-py-interpreter";
- public async Task RunAsync(string codeScript, CodeInterpretOptions? options = null)
+ public async Task RunAsync(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
- if (options?.UseMutex == true)
+ if (options?.UseLock == true)
{
return await _executor.ExecuteAsync(async () =>
{
- return await InnerRunCode(codeScript, options);
- }, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
+ return await InnerRunCode(codeScript, options, cancellationToken);
+ }, cancellationToken: cancellationToken);
}
- return await InnerRunCode(codeScript, options);
+ return await InnerRunCode(codeScript, options, cancellationToken);
}
public async Task GenerateCodeScriptAsync(string text, CodeGenerationOptions? options = null)
@@ -98,7 +98,7 @@ public async Task GenerateCodeScriptAsync(string text, Cod
#region Private methods
- private async Task InnerRunCode(string codeScript, CodeInterpretOptions? options = null)
+ private async Task InnerRunCode(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
var response = new CodeInterpretResponse();
var scriptName = options?.ScriptName ?? codeScript.SubstringMax(30);
@@ -109,11 +109,11 @@ private async Task InnerRunCode(string codeScript, CodeIn
if (options?.UseProcess == true)
{
- response = await CoreRunProcess(codeScript, options);
+ response = await CoreRunProcess(codeScript, options, cancellationToken);
}
else
{
- response = await CoreRunScript(codeScript, options);
+ response = await CoreRunScript(codeScript, options, cancellationToken);
}
_logger.LogWarning($"End running python code script in {Provider}: {scriptName}");
@@ -134,88 +134,90 @@ private async Task InnerRunCode(string codeScript, CodeIn
}
}
- private async Task CoreRunScript(string codeScript, CodeInterpretOptions? options = null)
+ private async Task CoreRunScript(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
_logger.LogWarning($"Begin {nameof(CoreRunScript)} in {Provider}: ${options?.ScriptName}");
- var token = options?.CancellationToken ?? CancellationToken.None;
- token.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- using (Py.GIL())
+ var execTask = Task.Factory.StartNew(() =>
{
- token.ThrowIfCancellationRequested();
-
- // Import necessary Python modules
- dynamic sys = Py.Import("sys");
- dynamic io = Py.Import("io");
-
- try
+ using (Py.GIL())
{
- // Redirect standard output/error to capture it
- dynamic stringIO = io.StringIO();
- sys.stdout = stringIO;
- sys.stderr = stringIO;
-
- // Set global items
- using var globals = new PyDict();
- if (codeScript.Contains("__main__") == true)
- {
- globals.SetItem("__name__", new PyString("__main__"));
- }
+ // Import necessary Python modules
+ dynamic sys = Py.Import("sys");
+ dynamic io = Py.Import("io");
- // Set arguments
- var list = new PyList();
- if (options?.Arguments?.Any() == true)
+ try
{
- list.Append(new PyString(options?.ScriptName ?? "script.py"));
+ // Redirect standard output/error to capture it
+ dynamic outIO = io.StringIO();
+ dynamic errIO = io.StringIO();
+ sys.stdout = outIO;
+ sys.stderr = errIO;
+
+ // Set global items
+ using var globals = new PyDict();
+ if (codeScript.Contains("__main__") == true)
+ {
+ globals.SetItem("__name__", new PyString("__main__"));
+ }
- foreach (var arg in options!.Arguments)
+ // Set arguments
+ var list = new PyList();
+ if (options?.Arguments?.Any() == true)
{
- if (!string.IsNullOrWhiteSpace(arg.Key) && !string.IsNullOrWhiteSpace(arg.Value))
+ list.Append(new PyString(options?.ScriptName ?? "script.py"));
+
+ foreach (var arg in options!.Arguments)
{
- list.Append(new PyString($"--{arg.Key}"));
- list.Append(new PyString($"{arg.Value}"));
+ if (!string.IsNullOrWhiteSpace(arg.Key) && !string.IsNullOrWhiteSpace(arg.Value))
+ {
+ list.Append(new PyString($"--{arg.Key}"));
+ list.Append(new PyString($"{arg.Value}"));
+ }
}
}
- }
- sys.argv = list;
+ sys.argv = list;
- token.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- // Execute Python script
- PythonEngine.Exec(codeScript, globals);
+ // Execute Python script
+ PythonEngine.Exec(codeScript, globals);
- // Get result
- var result = stringIO.getvalue()?.ToString() as string;
+ // Get result
+ var stdout = outIO.getvalue()?.ToString() as string;
+ var stderr = errIO.getvalue()?.ToString() as string;
- token.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- return new CodeInterpretResponse
+ return new CodeInterpretResponse
+ {
+ Result = stdout?.TrimEnd('\r', '\n') ?? string.Empty,
+ Success = true
+ };
+ }
+ catch (Exception ex)
{
- Result = result?.TrimEnd('\r', '\n') ?? string.Empty,
- Success = true
- };
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, $"Error in {nameof(CoreRunScript)} in {Provider}.");
- throw;
- }
- finally
- {
- // Restore the original stdout/stderr/argv
- sys.stdout = sys.__stdout__;
- sys.stderr = sys.__stderr__;
- sys.argv = new PyList();
- }
- };
+ _logger.LogError(ex, $"Error in {nameof(CoreRunScript)} in {Provider}.");
+ return new() { ErrorMsg = ex.Message };
+ }
+ finally
+ {
+ // Restore the original stdout/stderr/argv
+ sys.stdout = sys.__stdout__;
+ sys.stderr = sys.__stderr__;
+ sys.argv = new PyList();
+ }
+ };
+ }, cancellationToken);
+
+ return await execTask.WaitAsync(cancellationToken);
}
- private async Task CoreRunProcess(string codeScript, CodeInterpretOptions? options = null)
+ private async Task CoreRunProcess(string codeScript, CodeInterpretOptions? options = null, CancellationToken cancellationToken = default)
{
- var token = options?.CancellationToken ?? CancellationToken.None;
-
var psi = new ProcessStartInfo
{
FileName = "python",
@@ -252,7 +254,7 @@ private async Task CoreRunProcess(string codeScript, Code
try
{
- using var reg = token.Register(() =>
+ using var reg = cancellationToken.Register(() =>
{
try
{
@@ -264,12 +266,12 @@ private async Task CoreRunProcess(string codeScript, Code
catch { }
});
- var stdoutTask = proc.StandardOutput.ReadToEndAsync(token);
- var stderrTask = proc.StandardError.ReadToEndAsync(token);
+ var stdoutTask = proc.StandardOutput.ReadToEndAsync(cancellationToken);
+ var stderrTask = proc.StandardError.ReadToEndAsync(cancellationToken);
- await Task.WhenAll([proc.WaitForExitAsync(token), stdoutTask, stderrTask]);
+ await Task.WhenAll([proc.WaitForExitAsync(cancellationToken), stdoutTask, stderrTask]);
- token.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
return new CodeInterpretResponse
{