Skip to content

Conversation

@iceljc
Copy link
Collaborator

@iceljc iceljc commented Oct 8, 2025

PR Type

Enhancement


Description

  • Add new AI Programmer and File Assistant agents

  • Refactor Python interpreter with thread safety and mutex support

  • Improve code execution with better error handling

  • Reorganize agent templates and configurations


Diagram Walkthrough

flowchart LR
  A["New Agents"] --> B["AI Programmer"]
  A --> C["File Assistant"]
  D["Python Interpreter"] --> E["Thread Safety"]
  D --> F["Mutex Support"]
  D --> G["Better Error Handling"]
  H["Template Migration"] --> I["Agent-specific Templates"]
Loading

File Walkthrough

Relevant files
Enhancement
10 files
BuiltInAgentId.cs
Add new agent IDs for AI Programmer and File Assistant     
+10/-0   
CodeInterpretOptions.cs
Add script name, mutex and cancellation token options       
+5/-0     
CodeScriptExecutor.cs
Create new thread-safe code execution service                       
+34/-0   
FileInstructService.SelectFile.cs
Update agent ID and template references                                   
+2/-2     
InstructService.Execute.cs
Add script name parameter to code execution                           
+2/-1     
PlotChartFn.cs
Update template references to AI Programmer agent               
+2/-2     
PyProgrammerFn.cs
Refactor Python execution with better error handling         
+59/-30 
PythonInterpreterPlugin.cs
Improve Python engine initialization and cleanup                 
+11/-3   
PyInterpretService.cs
Add mutex support and improved error handling                       
+58/-20 
demo.py
Update demo script with JSON output format                             
+7/-2     
Configuration changes
7 files
ConversationPlugin.cs
Register CodeScriptExecutor as singleton service                 
+2/-0     
BotSharp.Core.csproj
Add new agent configuration files to project                         
+20/-5   
agent.json
Create AI Programmer agent configuration                                 
+18/-0   
agent.json
Create File Assistant agent configuration                               
+18/-0   
BotSharp.Plugin.ChartHandler.csproj
Update template file reference path                                           
+2/-2     
BotSharp.Plugin.FileHandler.csproj
Add new file selection template reference                               
+4/-0     
BotSharp.Plugin.PythonInterpreter.csproj
Update template file reference path                                           
+4/-2     
Documentation
3 files
instruction.liquid
Add AI Programmer agent instruction template                         
+1/-0     
instruction.liquid
Add File Assistant agent instruction template                       
+1/-0     
py-code_generate_instruction.liquid
Update Python code generation template text                           
+1/-1     
Miscellaneous
1 files
BotSharp.Core.Crontab.csproj
Remove unused function file reference                                       
+1/-0     
Additional files
2 files
chart-js-generate_instruction.liquid [link]   
select-chat-file_instruction.liquid [link]   

@iceljc iceljc marked this pull request as draft October 8, 2025 02:57
@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Oct 8, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Remote code execution

Description: Untrusted Python code is executed via PythonEngine.Exec without sandboxing or input
validation, which can allow arbitrary code execution if attackers can influence
codeScript.
PyInterpretService.cs [68-121]

Referred Code
try
{
    // 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__"));
    }

    // Set arguments
    var list = new PyList();
    if (options?.Arguments?.Any() == true)
    {
        list.Append(new PyString("code.py"));

        foreach (var arg in options.Arguments)


 ... (clipped 33 lines)
Remote code execution

Description: PythonEngine.Exec runs dynamically generated Python code without restrictions, enabling
arbitrary execution and system access if code source is not strictly controlled.
PyProgrammerFn.cs [105-146]

Referred Code
private (bool, string) InnerRunCode(string codeScript)
{
    using (Py.GIL())
    {
        // Import necessary Python modules
        dynamic sys = Py.Import("sys");
        dynamic io = Py.Import("io");

        try
        {
            // 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__"));
            }


 ... (clipped 21 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Oct 8, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate duplicated Python execution logic

Refactor PyProgrammerFn to use the ICodeInterpretService for Python execution.
This change will remove the duplicated script-running logic from PyProgrammerFn,
centralizing it within PyInterpretService and ensuring all executions are
thread-safe.

Examples:

src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs [105-139]
    private (bool, string) InnerRunCode(string codeScript)
    {
        using (Py.GIL())
        {
            // Import necessary Python modules
            dynamic sys = Py.Import("sys");
            dynamic io = Py.Import("io");

            try
            {

 ... (clipped 25 lines)
src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs [60-130]
    private CodeInterpretResult CoreRun(string codeScript, CodeInterpretOptions? options = null)
    {
        using (Py.GIL())
        {
            // Import necessary Python modules
            dynamic sys = Py.Import("sys");
            dynamic io = Py.Import("io");

            try
            {

 ... (clipped 61 lines)

Solution Walkthrough:

Before:

// In PyProgrammerFn.cs
public class PyProgrammerFn : IFunctionCallback
{
    public async Task<bool> Execute(RoleDialogModel message)
    {
        // ... logic to get python code ...
        var (isSuccess, result) = InnerRunCode(pythonCode);
        message.Content = result;
        // ...
    }

    private (bool, string) InnerRunCode(string codeScript)
    {
        using (Py.GIL())
        {
            // ... full python execution logic with try/catch/finally ...
            PythonEngine.Exec(codeScript, globals);
            // ...
        }
    }
}

After:

// In PyProgrammerFn.cs
public class PyProgrammerFn : IFunctionCallback
{
    private readonly ICodeInterpretService _pyInterpreter;

    public PyProgrammerFn(..., IServiceProvider services)
    {
        _pyInterpreter = services.GetRequiredService<ICodeInterpretService>();
    }

    public async Task<bool> Execute(RoleDialogModel message)
    {
        // ... logic to get python code ...
        var result = await _pyInterpreter.RunCode(pythonCode);
        message.Content = result.Success ? result.Result : result.ErrorMsg;
        // ...
    }

    // No InnerRunCode method needed
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies significant code duplication and a failure to centralize logic, as PyProgrammerFn re-implements Python execution instead of using the new thread-safe PyInterpretService, undermining a core goal of the PR.

High
Possible issue
Prevent race conditions during execution

In PyProgrammerFn, use the CodeScriptExecutor to wrap the Python code execution.
Resolve the executor from the IServiceProvider and use its Execute method to
prevent race conditions.

src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs [9-219]

 public class PyProgrammerFn : IFunctionCallback
 {
     public string Name => "util-code-python_programmer";
     public string Indication => "Programming and executing code";
 ...
     public PyProgrammerFn(
         IServiceProvider services,
         ILogger<PyProgrammerFn> logger,
         PythonInterpreterSettings settings)
 ...
     }
 
     public async Task<bool> Execute(RoleDialogModel message)
     {
 ...
         var response = await GetChatCompletion(innerAgent, dialogs);
         var ret = response.JsonContent<LlmContextOut>();
-
-        try
+        
+        var executor = _services.GetRequiredService<CodeScriptExecutor>();
+        var execResult = await executor.Execute(() =>
         {
             var (isSuccess, result) = InnerRunCode(ret.PythonCode);
-            if (isSuccess)
+            return Task.FromResult(new CodeInterpretResult 
+            { 
+                Success = isSuccess,
+                Result = result,
+                ErrorMsg = isSuccess ? null : result
+            });
+        });
+
+        if (execResult.Success)
+        {
+            message.Content = execResult.Result;
+            message.RichContent = new RichContent<IRichMessage>
             {
-...
-            }
-            else
-            {
-                message.Content = result;
-            }
+                Recipient = new Recipient { Id = convService.ConversationId },
+                Message = new ProgramCodeTemplateMessage
+                {
+                    Text = ret.PythonCode ?? string.Empty,
+                    Language = "python"
+                }
+            };
+            message.StopCompletion = true;
         }
-        catch (Exception ex)
+        else
         {
-            var errorMsg = $"Error when executing python code. {ex.Message}";
-            message.Content = errorMsg;
-            _logger.LogError(ex, errorMsg);
+            message.Content = execResult.ErrorMsg;
         }
 
         return true;
     }
 ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a missed use of the new CodeScriptExecutor, which leaves a critical race condition bug that the PR was intended to solve.

High
Learned
best practice
Remove unnecessary async/await

Avoid unnecessary async/await by passing a non-async delegate and ensure the
cancellation token is propagated directly. This reduces overhead and follows
async best practices.

src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs [31-34]

-return await _executor.Execute(async () =>
-{
-    return InnerRunCode(codeScript, options);
-}, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
+return await _executor.Execute(
+    () => Task.FromResult(InnerRunCode(codeScript, options)),
+    cancellationToken: options?.CancellationToken ?? CancellationToken.None
+);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Validate external/system state and respect cancellation to avoid invalid async usage and deadlocks.

Low
Use non-nullable CancellationToken

Use a non-nullable CancellationToken and default it to CancellationToken.None to
avoid nullable handling and potential null checks.

src/Infrastructure/BotSharp.Abstraction/CodeInterpreter/Models/CodeInterpretOptions.cs [8-9]

 public bool LockFree { get; set; }
-public CancellationToken? CancellationToken { get; set; }
+public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Ensure null-safety on optional inputs; prefer non-nullable CancellationToken over nullable wrapper with safe default.

Low
  • Update

@iceljc iceljc marked this pull request as ready for review October 8, 2025 20:26
@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Oct 8, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Invalid model config

Description: The agent configuration references non-existent or hypothetical LLM models such as "gpt-5"
which may cause runtime failures or fallback behavior in production if not validated
against actual provider offerings.
agent.json [1-18]

Referred Code
{
  "id": "c2a2faf6-b8b5-47fe-807b-f4714cf25dd4",
  "name": "AI programmer",
  "description": "AI programmer is designed to generate code scripts.",
  "type": "task",
  "createdDateTime": "2025-10-07T10:39:32Z",
  "updatedDateTime": "2025-10-07T14:39:32Z",
  "iconUrl": "/images/logo.png",
  "disabled": false,
  "isPublic": true,
  "llmConfig": {
    "is_inherit": false,
    "provider": "openai",
    "model": "gpt-5",
    "max_recursion_depth": 3,
    "reasoning_effort_level": "minimal"
  }
}
Arbitrary code execution

Description: Executing arbitrary Python code via PythonEngine.Exec without sandboxing or resource
limits can allow code execution attacks if untrusted input reaches this path; mutex only
serializes but does not sandbox or restrict filesystem/network access.
PyInterpretService.cs [27-37]

Referred Code
public async Task<CodeInterpretResult> RunCode(string codeScript, CodeInterpretOptions? options = null)
{
    if (options?.UseMutex == true)
    {
        return await _executor.Execute(async () =>
        {
            return InnerRunCode(codeScript, options);
        }, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
    }
    return InnerRunCode(codeScript, options);
}
Untrusted code execution

Description: Direct execution of generated Python code (ret.PythonCode) without isolation or permission
controls can lead to execution of malicious code, exfiltration, or system compromise when
prompts are user-influenced.
PyProgrammerFn.cs [100-141]

Referred Code
/// Run python code script => (isSuccess, result)
/// </summary>
/// <param name="codeScript"></param>
/// <returns></returns>
private (bool, string) InnerRunCode(string codeScript)
{
    using (Py.GIL())
    {
        // Import necessary Python modules
        dynamic sys = Py.Import("sys");
        dynamic io = Py.Import("io");

        try
        {
            // 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();


 ... (clipped 21 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Oct 8, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate duplicated Python execution logic

The PyProgrammerFn class duplicates Python execution logic already present in
the refactored PyInterpretService, missing out on new thread-safety features. It
should be updated to use the ICodeInterpretService instead.

Examples:

src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs [99-147]
    /// <summary>
    /// Run python code script => (isSuccess, result)
    /// </summary>
    /// <param name="codeScript"></param>
    /// <returns></returns>
    private (bool, string) InnerRunCode(string codeScript)
    {
        using (Py.GIL())
        {
            // Import necessary Python modules

 ... (clipped 39 lines)
src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs [27-128]
    public async Task<CodeInterpretResult> RunCode(string codeScript, CodeInterpretOptions? options = null)
    {
        if (options?.UseMutex == true)
        {
            return await _executor.Execute(async () =>
            {
                return InnerRunCode(codeScript, options);
            }, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
        }
        return InnerRunCode(codeScript, options);

 ... (clipped 92 lines)

Solution Walkthrough:

Before:

// In src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs
public class PyProgrammerFn : IFunctionCallback
{
    public async Task<bool> Execute(RoleDialogModel message)
    {
        // ... logic to get python code ...
        var (isSuccess, result) = InnerRunCode(ret.PythonCode);
        // ... handle result ...
        return true;
    }

    private (bool, string) InnerRunCode(string codeScript)
    {
        using (Py.GIL())
        {
            // ... sets up python environment ...
            PythonEngine.Exec(codeScript, globals);
            // ... gets result and returns ...
        }
    }
}

After:

// In src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs
public class PyProgrammerFn : IFunctionCallback
{
    private readonly ICodeInterpretService _codeInterpreter;

    public PyProgrammerFn(IServiceProvider services, /*...,*/ ICodeInterpretService codeInterpreter)
    {
        _services = services;
        _codeInterpreter = codeInterpreter; // Injected via DI
    }

    public async Task<bool> Execute(RoleDialogModel message)
    {
        // ... logic to get python code ...
        var result = await _codeInterpreter.RunCode(ret.PythonCode);
        // ... handle result ...
        return true;
    }

    // InnerRunCode method is removed.
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a significant design flaw where PyProgrammerFn duplicates logic and bypasses the new thread-safe PyInterpretService, undermining a primary goal of the PR.

High
Possible issue
Fix incorrect Python argument formatting

In the CoreRun method, modify the Python argument construction to append the key
(prefixed with --) and value as separate items to the PyList, ensuring they are
correctly parsed by argparse.

src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs [58-128]

 private CodeInterpretResult CoreRun(string codeScript, CodeInterpretOptions? options = null)
 {
     using (Py.GIL())
     {
         // Import necessary Python modules
         dynamic sys = Py.Import("sys");
         dynamic io = Py.Import("io");
 
         try
         {
             // 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__"));
             }
 
             // Set arguments
             var list = new PyList();
             if (options?.Arguments?.Any() == true)
             {
                 list.Append(new PyString(options?.ScriptName.IfNullOrEmptyAs("script.py")));
 
                 foreach (var arg in options.Arguments)
                 {
                     if (!string.IsNullOrWhiteSpace(arg.Key) && !string.IsNullOrWhiteSpace(arg.Value))
                     {
-                        list.Append(new PyString($"{arg.Key}={arg.Value}"));
+                        list.Append(new PyString($"--{arg.Key}"));
+                        list.Append(new PyString(arg.Value));
                     }
                 }
                 sys.argv = list;
             }
 
             // Execute Python script
             PythonEngine.Exec(codeScript, globals);
 
             // Get result
             var result = stringIO.getvalue()?.ToString() as string;
 
             return new CodeInterpretResult
             {
                 Result = result?.TrimEnd('\r', '\n'),
                 Success = true
             };
         }
         catch (Exception ex)
         {
             var errorMsg = $"Error when executing core python code in {nameof(PyInterpretService)}: {Provider}. {ex.Message}";
             _logger.LogError(ex, errorMsg);
 
             return new CodeInterpretResult
             {
                 Success = false,
                 ErrorMsg = errorMsg
             };
         }
         finally
         {
             // Restore the original stdout/stderr/argv
             sys.stdout = sys.__stdout__;
             sys.stderr = sys.__stderr__;
             sys.argv = new PyList();
         }
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug in how command-line arguments are passed to the Python script, which would cause argument parsing to fail. The proposed fix correctly formats the arguments for Python's argparse.

High
Re-throw exception to avoid silent failures

In the Execute method, re-throw exceptions instead of catching them and
returning default(T) to prevent silent failures and subsequent
NullReferenceExceptions in the calling code.

src/Infrastructure/BotSharp.Core/CodeInterpreter/CodeScriptExecutor.cs [16-33]

 public async Task<T> Execute<T>(Func<Task<T>> func, CancellationToken cancellationToken = default)
 {
     await _semLock.WaitAsync(cancellationToken);
 
     try
     {
         return await func();
     }
     catch (Exception ex)
     {
         _logger.LogError(ex, $"Error in {nameof(CodeScriptExecutor)}.");
-        return default(T);
+        throw;
     }
     finally
     {
         _semLock.Release();
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that swallowing exceptions and returning default(T) is a bug that will cause a NullReferenceException in the calling code, and proposes the correct fix of re-throwing the exception.

Medium
Learned
best practice
Guard interpreter with mutex and cancellation

Use a consistent guard when interacting with the embedded interpreter and ensure
cancellation tokens are respected even when the mutex is not requested. Default
to guarded execution for interpreter calls.

src/Plugins/BotSharp.Plugin.PythonInterpreter/Services/PyInterpretService.cs [27-37]

 public async Task<CodeInterpretResult> RunCode(string codeScript, CodeInterpretOptions? options = null)
 {
-    if (options?.UseMutex == true)
+    var ct = options?.CancellationToken ?? CancellationToken.None;
+    // Always honor cancellation
+    ct.ThrowIfCancellationRequested();
+
+    // Default to guarded execution unless explicitly disabled
+    var useMutex = options?.UseMutex ?? true;
+    if (useMutex)
     {
-        return await _executor.Execute(async () =>
-        {
-            return InnerRunCode(codeScript, options);
-        }, cancellationToken: options?.CancellationToken ?? CancellationToken.None);
+        return await _executor.Execute(() => Task.FromResult(InnerRunCode(codeScript, options)), cancellationToken: ct);
     }
+
+    // Unguarded path still honors cancellation
+    ct.ThrowIfCancellationRequested();
     return InnerRunCode(codeScript, options);
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Validate external/system state and asynchronous usage; avoid blocking or unsafe concurrent access to shared interpreter resources by guarding with a mutex and honoring cancellation tokens.

Low
  • More

@iceljc iceljc merged commit 88b18c0 into SciSharp:master Oct 8, 2025
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant