Files
herolib/libwip/mcp_rhai/example/example.vsh
2025-09-14 17:57:06 +02:00

597 lines
19 KiB
GLSL
Executable File

#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.ai.mcp.aitools.escalayer
import os
fn main() {
// Get the current directory where this script is located
current_dir := os.dir(@FILE)
// Validate command line arguments
source_code_path := validate_command_args() or {
println(err)
return
}
// Read and combine all Rust files in the source directory
source_code := read_source_code(source_code_path) or {
println(err)
return
}
// Determine the crate path from the source code path
crate_path := determine_crate_path(source_code_path) or {
println(err)
return
}
// Extract the module name from the directory path (last component)
name := extract_module_name_from_path(source_code_path)
// Create the prompt content for the AI
prompt_content := create_rhai_wrappers(name, source_code, read_file_safely('${current_dir}/prompts/example_script.md'),
read_file_safely('${current_dir}/prompts/wrapper.md'), read_file_safely('${current_dir}/prompts/errors.md'),
crate_path)
// Create the generator instance
gen := RhaiGen{
name: name
dir: source_code_path
}
// Run the task to generate Rhai wrappers
run_wrapper_generation_task(prompt_content, gen) or {
println('Task failed: ${err}')
return
}
println('Task completed successfully')
println('The wrapper files have been generated and compiled in the target directory.')
println('Check /Users/timurgordon/code/git.threefold.info/herocode/sal/src/rhai for the compiled output.')
}
// Validates command line arguments and returns the source code path
fn validate_command_args() !string {
if os.args.len < 2 {
return error('Please provide the path to the source code directory as an argument\nExample: ./example.vsh /path/to/source/code/directory')
}
source_code_path := os.args[1]
if !os.exists(source_code_path) {
return error('Source code path does not exist: ${source_code_path}')
}
if !os.is_dir(source_code_path) {
return error('Source code path is not a directory: ${source_code_path}')
}
return source_code_path
}
// Reads and combines all Rust files in the given directory
fn read_source_code(source_code_path string) !string {
// Get all files in the directory
files := os.ls(source_code_path) or {
return error('Failed to list files in directory: ${err}')
}
// Combine all Rust files into a single source code string
mut source_code := ''
for file in files {
file_path := os.join_path(source_code_path, file)
// Skip directories and non-Rust files
if os.is_dir(file_path) || !file.ends_with('.rs') {
continue
}
// Read the file content
file_content := os.read_file(file_path) or {
println('Failed to read file ${file_path}: ${err}')
continue
}
// Add file content to the combined source code
source_code += '// File: ${file}\n${file_content}\n\n'
}
if source_code == '' {
return error('No Rust files found in directory: ${source_code_path}')
}
return source_code
}
// Determines the crate path from the source code path
fn determine_crate_path(source_code_path string) !string {
// Extract the path relative to the src directory
src_index := source_code_path.index('src/') or {
return error('Could not determine crate path: src/ not found in path')
}
mut path_parts := source_code_path[src_index + 4..].split('/')
// Remove the last part (the file name)
if path_parts.len > 0 {
path_parts.delete_last()
}
rel_path := path_parts.join('::')
return 'sal::${rel_path}'
}
// Extracts the module name from a directory path
fn extract_module_name_from_path(path string) string {
dir_parts := path.split('/')
return dir_parts[dir_parts.len - 1]
}
// Helper function to read a file or return empty string if file doesn't exist
fn read_file_safely(file_path string) string {
return os.read_file(file_path) or { '' }
}
// Runs the task to generate Rhai wrappers
fn run_wrapper_generation_task(prompt_content string, gen RhaiGen) !string {
// Create a new task
mut task := escalayer.new_task(
name: 'rhai_wrapper_creator.escalayer'
description: 'Create Rhai wrappers for Rust functions that follow builder pattern and create examples corresponding to the provided example file'
)
// Create model configs
sonnet_model := escalayer.ModelConfig{
name: 'anthropic/claude-3.7-sonnet'
provider: 'anthropic'
temperature: 0.7
max_tokens: 25000
}
gpt4_model := escalayer.ModelConfig{
name: 'gpt-4'
provider: 'openai'
temperature: 0.7
max_tokens: 25000
}
// Create a prompt function that returns the prepared content
prompt_function := fn [prompt_content] (input string) string {
return prompt_content
}
// Define a single unit task that handles everything
task.new_unit_task(
name: 'create_rhai_wrappers'
prompt_function: prompt_function
callback_function: gen.process_rhai_wrappers
base_model: sonnet_model
retry_model: gpt4_model
retry_count: 1
)
// Initiate the task
return task.initiate('')
}
// Define a Rhai wrapper generator function for Container functions
fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string {
// Load all required template and guide files
guides := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
engine := $tmpl('./prompts/engine.md')
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
// Build the prompt content
return build_prompt_content(name, source_code, example_rhai, wrapper_md, errors_md,
guides, vector_vs_array, rhai_integration_fixes, rhai_syntax_guide, generic_wrapper_rs,
engine)
}
// Helper function to load guide files with error handling
fn load_guide_file(path string) string {
return os.read_file(path) or {
eprintln('Warning: Failed to read guide file: ${path}')
return ''
}
}
// Builds the prompt content for the AI
fn build_prompt_content(name string, source_code string, example_rhai string, wrapper_md string,
errors_md string, guides string, vector_vs_array string,
rhai_integration_fixes string, rhai_syntax_guide string,
generic_wrapper_rs string, engine string) string {
return 'You are a Rust developer tasked with creating Rhai wrappers for Rust functions. Please review the following best practices for Rhai wrappers and then create the necessary files.
${guides}
${vector_vs_array}
${example_rhai}
${wrapper_md}
## Common Errors to Avoid
${errors_md}
${rhai_integration_fixes}
${rhai_syntax_guide}
## Your Task
Please create a wrapper.rs file that implements Rhai wrappers for the provided Rust code, and an example.rhai script that demonstrates how to use these wrappers:
## Rust Code to Wrap
```rust
${source_code}
```
IMPORTANT NOTES:
1. For Rhai imports, use: `use rhai::{Engine, EvalAltResult, plugin::*, Dynamic, Map, Array};` - only import what you actually use
2. The following dependencies are available in Cargo.toml:
- rhai = "1.21.0"
- serde = { version = "1.0", features = ["derive"] }
- serde_json = "1.0"
- sal = { path = "../../../" }
3. For the wrapper: `use sal::${name};` this way you can access the module functions and objects with ${name}::
4. The generic_wrapper.rs file will be hardcoded into the package, you can use code from there.
```rust
${generic_wrapper_rs}
```
5. IMPORTANT: Prefer strongly typed return values over Dynamic types whenever possible. Only use Dynamic when absolutely necessary.
- For example, return `Result<String, Box<EvalAltResult>>` instead of `Dynamic` when a function returns a string
- Use `Result<bool, Box<EvalAltResult>>` instead of `Dynamic` when a function returns a boolean
- Use `Result<Vec<String>, Box<EvalAltResult>>` instead of `Dynamic` when a function returns a list of strings
6. Your code should include public functions that can be called from Rhai scripts
7. Make sure to implement all necessary helper functions for type conversion
8. DO NOT use the #[rhai_fn] attribute - functions will be registered directly in the engine
9. Make sure to handle string type consistency - use String::from() for string literals when returning in match arms with format!() strings
10. When returning path references, convert them to owned strings (e.g., path().to_string())
11. For error handling, use proper Result types with Box<EvalAltResult> for the error type:
```rust
// INCORRECT:
pub fn some_function(arg: &str) -> Dynamic {
match some_operation(arg) {
Ok(result) => Dynamic::from(result),
Err(err) => Dynamic::from(format!("Error: {}", err))
}
}
// CORRECT:
pub fn some_function(arg: &str) -> Result<String, Box<EvalAltResult>> {
some_operation(arg).map_err(|err| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Error: {}", err).into(),
rhai::Position::NONE
))
})
}
```
12. IMPORTANT: Format your response with the code between triple backticks as follows:
```rust
// wrapper.rs
// Your wrapper implementation here
```
```rust
// engine.rs
// Your engine.rs implementation here
```
```rhai
// example.rhai
// Your example Rhai script here
```
13. The example.rhai script should demonstrate the use of all the wrapper functions you create
14. The engine.rs file should contain a register_module function that registers all the wrapper functions and types with the Rhai engine, and a create function. For example:
${engine}
MOST IMPORTANT:
import package being wrapped as `use sal::<n>`
your engine create function is called `create_rhai_engine`
```
'
}
@[params]
pub struct WrapperModule {
pub:
lib_rs string
example_rs string
engine_rs string
cargo_toml string
example_rhai string
generic_wrapper_rs string
wrapper_rs string
}
// functions is a list of function names that AI should extract and pass in
fn create_wrapper_module(wrapper WrapperModule, functions []string, name_ string, base_dir string) !string {
// Define project directory paths
name := name_
project_dir := '${base_dir}/rhai'
// Create the project using cargo new --lib
if os.exists(project_dir) {
os.rmdir_all(project_dir) or {
return error('Failed to clean existing project directory: ${err}')
}
}
// Run cargo new --lib to create the project
os.chdir(base_dir) or { return error('Failed to change directory to base directory: ${err}') }
cargo_new_result := os.execute('cargo new --lib rhai')
if cargo_new_result.exit_code != 0 {
return error('Failed to create new library project: ${cargo_new_result.output}')
}
// Create examples directory
examples_dir := '${project_dir}/examples'
os.mkdir_all(examples_dir) or { return error('Failed to create examples directory: ${err}') }
// Write the lib.rs file
if wrapper.lib_rs != '' {
os.write_file('${project_dir}/src/lib.rs', wrapper.lib_rs) or {
return error('Failed to write lib.rs: ${err}')
}
}
// Write the wrapper.rs file
if wrapper.wrapper_rs != '' {
os.write_file('${project_dir}/src/wrapper.rs', wrapper.wrapper_rs) or {
return error('Failed to write wrapper.rs: ${err}')
}
}
// Write the generic wrapper.rs file
if wrapper.generic_wrapper_rs != '' {
os.write_file('${project_dir}/src/generic_wrapper.rs', wrapper.generic_wrapper_rs) or {
return error('Failed to write generic wrapper.rs: ${err}')
}
}
// Write the example.rs file
if wrapper.example_rs != '' {
os.write_file('${examples_dir}/example.rs', wrapper.example_rs) or {
return error('Failed to write example.rs: ${err}')
}
}
// Write the engine.rs file if provided
if wrapper.engine_rs != '' {
os.write_file('${project_dir}/src/engine.rs', wrapper.engine_rs) or {
return error('Failed to write engine.rs: ${err}')
}
}
// Write the Cargo.toml file
if wrapper.cargo_toml != '' {
os.write_file('${project_dir}/Cargo.toml', wrapper.cargo_toml) or {
return error('Failed to write Cargo.toml: ${err}')
}
}
// Write the example.rhai file if provided
if wrapper.example_rhai != '' {
os.write_file('${examples_dir}/example.rhai', wrapper.example_rhai) or {
return error('Failed to write example.rhai: ${err}')
}
}
return project_dir
}
// Helper function to extract code blocks from the response
fn extract_code_block(response string, identifier string, language string) string {
// Find the start marker for the code block
mut start_marker := '```${language}\n// ${identifier}'
if language == '' {
start_marker = '```\n// ${identifier}'
}
start_index := response.index(start_marker) or {
// Try alternative format
mut alt_marker := '```${language}\n${identifier}'
if language == '' {
alt_marker = '```\n${identifier}'
}
response.index(alt_marker) or { return '' }
}
// Find the end marker
end_marker := '```'
end_index := response.index_after(end_marker, start_index + start_marker.len) or { return '' }
// Extract the content between the markers
content_start := start_index + start_marker.len
content := response[content_start..end_index].trim_space()
return content
}
// Extract module name from wrapper code
fn extract_module_name(code string) string {
lines := code.split('\n')
for line in lines {
// Look for pub mod or mod declarations
if line.contains('pub mod ') || line.contains('mod ') {
// Extract module name
mut parts := []string{}
if line.contains('pub mod ') {
parts = line.split('pub mod ')
} else {
parts = line.split('mod ')
}
if parts.len > 1 {
// Extract the module name and remove any trailing characters
mut name := parts[1].trim_space()
// Remove any trailing { or ; or whitespace
name = name.trim_right('{').trim_right(';').trim_space()
if name != '' {
return name
}
}
}
}
return ''
}
// RhaiGen struct for generating Rhai wrappers
struct RhaiGen {
name string
dir string
}
// Process the AI response and compile the generated code
fn (gen RhaiGen) process_rhai_wrappers(response string) !string {
// Extract code blocks from the response
code_blocks := extract_code_blocks(response) or { return err }
// Extract function names from the wrapper.rs content
functions := extract_functions_from_code(code_blocks.wrapper_rs)
println('Using module name: ${gen.name}_rhai')
println('Extracted functions: ${functions.join(', ')}')
name := gen.name
// Create a WrapperModule struct with the extracted content
wrapper := WrapperModule{
lib_rs: $tmpl('./templates/lib.rs')
wrapper_rs: code_blocks.wrapper_rs
example_rs: $tmpl('./templates/example.rs')
engine_rs: code_blocks.engine_rs
generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs')
cargo_toml: $tmpl('./templates/cargo.toml')
example_rhai: code_blocks.example_rhai
}
// Create the wrapper module
project_dir := create_wrapper_module(wrapper, functions, gen.name, gen.dir) or {
return error('Failed to create wrapper module: ${err}')
}
// Build and run the project
build_output, run_output := build_and_run_project(project_dir) or { return err }
return format_success_message(project_dir, build_output, run_output)
}
// CodeBlocks struct to hold extracted code blocks
struct CodeBlocks {
wrapper_rs string
engine_rs string
example_rhai string
}
// Extract code blocks from the AI response
fn extract_code_blocks(response string) !CodeBlocks {
// Extract wrapper.rs content
wrapper_rs_content := extract_code_block(response, 'wrapper.rs', 'rust')
if wrapper_rs_content == '' {
return error('Failed to extract wrapper.rs content from response. Please ensure your code is properly formatted inside a code block that starts with ```rust\n// wrapper.rs and ends with ```')
}
// Extract engine.rs content
mut engine_rs_content := extract_code_block(response, 'engine.rs', 'rust')
if engine_rs_content == '' {
// Try to extract from the response without explicit language marker
engine_rs_content = extract_code_block(response, 'engine.rs', '')
}
// Extract example.rhai content
mut example_rhai_content := extract_code_block(response, 'example.rhai', 'rhai')
if example_rhai_content == '' {
// Try to extract from the response without explicit language marker
example_rhai_content = extract_code_block(response, 'example.rhai', '')
if example_rhai_content == '' {
// Use the example from the template
example_rhai_content = load_example_from_template() or { return err }
}
}
return CodeBlocks{
wrapper_rs: wrapper_rs_content
engine_rs: engine_rs_content
example_rhai: example_rhai_content
}
}
// Load example.rhai from template file
fn load_example_from_template() !string {
example_script_md := os.read_file('${os.dir(@FILE)}/prompts/example_script.md') or {
return error('Failed to read example.rhai template: ${err}')
}
// Extract the code block from the markdown file
example_rhai_content := extract_code_block(example_script_md, 'example.rhai', 'rhai')
if example_rhai_content == '' {
return error('Failed to extract example.rhai from template file')
}
return example_rhai_content
}
// Build and run the project
fn build_and_run_project(project_dir string) !(string, string) {
// Change to the project directory
os.chdir(project_dir) or { return error('Failed to change directory to project: ${err}') }
// Run cargo build first
build_result := os.execute('cargo build')
if build_result.exit_code != 0 {
return error('Compilation failed. Please fix the following errors and ensure your code is compatible with the existing codebase:\n\n${build_result.output}')
}
// Run the example
run_result := os.execute('cargo run --example example')
return build_result.output, run_result.output
}
// Format success message
fn format_success_message(project_dir string, build_output string, run_output string) string {
return 'Successfully generated Rhai wrappers and ran the example!\n\nProject created at: ${project_dir}\n\nBuild output:\n${build_output}\n\nRun output:\n${run_output}'
}
// Extract function names from wrapper code
fn extract_functions_from_code(code string) []string {
mut functions := []string{}
lines := code.split('\n')
for line in lines {
if line.contains('pub fn ') && !line.contains('//') {
// Extract function name
parts := line.split('pub fn ')
if parts.len > 1 {
name_parts := parts[1].split('(')
if name_parts.len > 0 {
fn_name := name_parts[0].trim_space()
if fn_name != '' {
functions << fn_name
}
}
}
}
}
return functions
}