...
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
## Baobab MCP
|
||||
|
||||
The Base Object and Actor Backend MCP Server provides tools to easily generate BaObAB modules for a given OpenAPI or OpenRPC Schema.
|
||||
@@ -1,172 +0,0 @@
|
||||
module baobab
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import x.json2 as json { Any }
|
||||
import freeflowuniverse.herolib.baobab.generator
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
|
||||
// generate_methods_file MCP Tool
|
||||
//
|
||||
|
||||
const generate_methods_file_tool = mcp.Tool{
|
||||
name: 'generate_methods_file'
|
||||
description: 'Generates a methods file with methods for a backend corresponding to thos specified in an OpenAPI or OpenRPC specification'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'source': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'openapi_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'openrpc_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
required: ['source']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &Baobab) generate_methods_file_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
source := json.decode[generator.Source](arguments['source'].str())!
|
||||
result := generator.generate_methods_file_str(source) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// generate_module_from_openapi MCP Tool
|
||||
const generate_module_from_openapi_tool = mcp.Tool{
|
||||
name: 'generate_module_from_openapi'
|
||||
description: ''
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'openapi_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['openapi_path']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &Baobab) generate_module_from_openapi_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
openapi_path := arguments['openapi_path'].str()
|
||||
result := generator.generate_module_from_openapi(openapi_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// generate_methods_interface_file MCP Tool
|
||||
const generate_methods_interface_file_tool = mcp.Tool{
|
||||
name: 'generate_methods_interface_file'
|
||||
description: 'Generates a methods interface file with method interfaces for a backend corresponding to those specified in an OpenAPI or OpenRPC specification'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'source': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'openapi_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'openrpc_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
required: ['source']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &Baobab) generate_methods_interface_file_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
source := json.decode[generator.Source](arguments['source'].str())!
|
||||
result := generator.generate_methods_interface_file_str(source) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// generate_model_file MCP Tool
|
||||
const generate_model_file_tool = mcp.Tool{
|
||||
name: 'generate_model_file'
|
||||
description: 'Generates a model file with data structures for a backend corresponding to those specified in an OpenAPI or OpenRPC specification'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'source': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'openapi_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'openrpc_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
required: ['source']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &Baobab) generate_model_file_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
source := json.decode[generator.Source](arguments['source'].str())!
|
||||
result := generator.generate_model_file_str(source) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// generate_methods_example_file MCP Tool
|
||||
const generate_methods_example_file_tool = mcp.Tool{
|
||||
name: 'generate_methods_example_file'
|
||||
description: 'Generates a methods example file with example implementations for a backend corresponding to those specified in an OpenAPI or OpenRPC specification'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'source': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'openapi_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'openrpc_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
required: ['source']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &Baobab) generate_methods_example_file_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
source := json.decode[generator.Source](arguments['source'].str())!
|
||||
result := generator.generate_methods_example_file_str(source) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
module baobab
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import json
|
||||
import x.json2
|
||||
import os
|
||||
|
||||
// This file contains tests for the Baobab tools implementation.
|
||||
// It tests the tools' ability to handle tool calls and return expected results.
|
||||
|
||||
// test_generate_module_from_openapi_tool tests the generate_module_from_openapi tool definition
|
||||
fn test_generate_module_from_openapi_tool() {
|
||||
// Verify the tool definition
|
||||
assert generate_module_from_openapi_tool.name == 'generate_module_from_openapi', 'Tool name should be "generate_module_from_openapi"'
|
||||
|
||||
// Verify the input schema
|
||||
assert generate_module_from_openapi_tool.input_schema.typ == 'object', 'Input schema type should be "object"'
|
||||
assert 'openapi_path' in generate_module_from_openapi_tool.input_schema.properties, 'Input schema should have "openapi_path" property'
|
||||
assert generate_module_from_openapi_tool.input_schema.properties['openapi_path'].typ == 'string', 'openapi_path property should be of type "string"'
|
||||
assert 'openapi_path' in generate_module_from_openapi_tool.input_schema.required, 'openapi_path should be a required property'
|
||||
}
|
||||
|
||||
// test_generate_module_from_openapi_tool_handler_error tests the error handling of the generate_module_from_openapi tool handler
|
||||
fn test_generate_module_from_openapi_tool_handler_error() {
|
||||
// Create arguments with a non-existent file path
|
||||
mut arguments := map[string]json2.Any{}
|
||||
arguments['openapi_path'] = json2.Any('non_existent_file.yaml')
|
||||
|
||||
// Call the handler
|
||||
result := generate_module_from_openapi_tool_handler(arguments) or {
|
||||
// If the handler returns an error, that's expected
|
||||
assert err.msg().contains(''), 'Error message should not be empty'
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, the handler should have returned an error result
|
||||
assert result.is_error, 'Result should indicate an error'
|
||||
assert result.content.len > 0, 'Error content should not be empty'
|
||||
assert result.content[0].typ == 'text', 'Error content should be of type "text"'
|
||||
assert result.content[0].text.contains('failed to open file'), 'Error content should contain "failed to open file", instead ${result.content[0].text}'
|
||||
}
|
||||
|
||||
// test_mcp_tool_call_integration tests the integration of the tool with the MCP server
|
||||
fn test_mcp_tool_call_integration() {
|
||||
// Create a new MCP server
|
||||
mut server := new_mcp_server() or {
|
||||
assert false, 'Failed to create MCP server: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary OpenAPI file for testing
|
||||
temp_dir := os.temp_dir()
|
||||
temp_file := os.join_path(temp_dir, 'test_openapi.yaml')
|
||||
os.write_file(temp_file, 'openapi: 3.0.0\ninfo:\n title: Test API\n version: 1.0.0\npaths:\n /test:\n get:\n summary: Test endpoint\n responses:\n "200":\n description: OK') or {
|
||||
assert false, 'Failed to create temporary file: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Sample tool call request
|
||||
tool_call_request := '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"generate_module_from_openapi","arguments":{"openapi_path":"${temp_file}"}}}'
|
||||
|
||||
// Process the request through the handler
|
||||
response := server.handler.handle(tool_call_request) or {
|
||||
// Clean up the temporary file
|
||||
os.rm(temp_file) or {}
|
||||
|
||||
// If the handler returns an error, that's expected in this test environment
|
||||
// since we might not have all dependencies set up
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up the temporary file
|
||||
os.rm(temp_file) or {}
|
||||
|
||||
// Decode the response to verify its structure
|
||||
decoded_response := jsonrpc.decode_response(response) or {
|
||||
// In a test environment, we might get an error due to missing dependencies
|
||||
// This is acceptable for this test
|
||||
return
|
||||
}
|
||||
|
||||
// If we got a successful response, verify it
|
||||
if !decoded_response.is_error() {
|
||||
// Parse the result to verify its contents
|
||||
result_json := decoded_response.result() or {
|
||||
assert false, 'Failed to get result: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the result to check the content
|
||||
result_map := json2.raw_decode(result_json) or {
|
||||
assert false, 'Failed to decode result: ${err}'
|
||||
return
|
||||
}.as_map()
|
||||
|
||||
// Verify the result structure
|
||||
assert 'isError' in result_map, 'Result should have isError field'
|
||||
assert 'content' in result_map, 'Result should have content field'
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
module baobab
|
||||
|
||||
import cli
|
||||
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'baobab'
|
||||
// execute: cmd_mcpgen
|
||||
description: 'baobab command'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Baobab server'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
mut server := new_mcp_server(&Baobab{})!
|
||||
server.start()!
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
module baobab
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import json
|
||||
import x.json2
|
||||
|
||||
// This file contains tests for the Baobab MCP server implementation.
|
||||
// It tests the server's ability to initialize and handle tool calls.
|
||||
|
||||
// test_new_mcp_server tests the creation of a new MCP server for the Baobab module
|
||||
fn test_new_mcp_server() {
|
||||
// Create a new MCP server
|
||||
mut server := new_mcp_server() or {
|
||||
assert false, 'Failed to create MCP server: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Verify server info
|
||||
assert server.server_info.name == 'developer', 'Server name should be "developer"'
|
||||
assert server.server_info.version == '1.0.0', 'Server version should be 1.0.0'
|
||||
|
||||
// Verify server capabilities
|
||||
assert server.capabilities.prompts.list_changed == true, 'Prompts capability should have list_changed set to true'
|
||||
assert server.capabilities.resources.subscribe == true, 'Resources capability should have subscribe set to true'
|
||||
assert server.capabilities.resources.list_changed == true, 'Resources capability should have list_changed set to true'
|
||||
assert server.capabilities.tools.list_changed == true, 'Tools capability should have list_changed set to true'
|
||||
}
|
||||
|
||||
// test_mcp_server_initialize tests the initialize handler with a sample initialize request
|
||||
fn test_mcp_server_initialize() {
|
||||
// Create a new MCP server
|
||||
mut server := new_mcp_server() or {
|
||||
assert false, 'Failed to create MCP server: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Sample initialize request from the MCP specification
|
||||
initialize_request := '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"sampling":{},"roots":{"listChanged":true}},"clientInfo":{"name":"mcp-inspector","version":"0.0.1"}}}'
|
||||
|
||||
// Process the request through the handler
|
||||
response := server.handler.handle(initialize_request) or {
|
||||
assert false, 'Handler failed to process request: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the response to verify its structure
|
||||
decoded_response := jsonrpc.decode_response(response) or {
|
||||
assert false, 'Failed to decode response: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the response is not an error
|
||||
assert !decoded_response.is_error(), 'Response should not be an error'
|
||||
|
||||
// Parse the result to verify its contents
|
||||
result_json := decoded_response.result() or {
|
||||
assert false, 'Failed to get result: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the result into an ServerConfiguration struct
|
||||
result := json.decode(mcp.ServerConfiguration, result_json) or {
|
||||
assert false, 'Failed to decode result: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the protocol version matches what was requested
|
||||
assert result.protocol_version == '2024-11-05', 'Protocol version should match the request'
|
||||
|
||||
// Verify server info
|
||||
assert result.server_info.name == 'developer', 'Server name should be "developer"'
|
||||
}
|
||||
|
||||
// test_tools_list tests the tools/list handler to verify tool registration
|
||||
fn test_tools_list() {
|
||||
// Create a new MCP server
|
||||
mut server := new_mcp_server() or {
|
||||
assert false, 'Failed to create MCP server: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Sample tools/list request
|
||||
tools_list_request := '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"cursor":""}}'
|
||||
|
||||
// Process the request through the handler
|
||||
response := server.handler.handle(tools_list_request) or {
|
||||
assert false, 'Handler failed to process request: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the response to verify its structure
|
||||
decoded_response := jsonrpc.decode_response(response) or {
|
||||
assert false, 'Failed to decode response: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that the response is not an error
|
||||
assert !decoded_response.is_error(), 'Response should not be an error'
|
||||
|
||||
// Parse the result to verify its contents
|
||||
result_json := decoded_response.result() or {
|
||||
assert false, 'Failed to get result: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the result into a map to check the tools
|
||||
result_map := json2.raw_decode(result_json) or {
|
||||
assert false, 'Failed to decode result: ${err}'
|
||||
return
|
||||
}.as_map()
|
||||
|
||||
// Verify that the tools array exists and contains the expected tool
|
||||
tools := result_map['tools'].arr()
|
||||
assert tools.len > 0, 'Tools list should not be empty'
|
||||
|
||||
// Find the generate_module_from_openapi tool
|
||||
mut found_tool := false
|
||||
for tool in tools {
|
||||
tool_map := tool.as_map()
|
||||
if tool_map['name'].str() == 'generate_module_from_openapi' {
|
||||
found_tool = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert found_tool, 'generate_module_from_openapi tool should be registered'
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
module baobab
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.ai.mcp.logger
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
@[heap]
|
||||
pub struct Baobab {}
|
||||
|
||||
pub fn new_mcp_server(v &Baobab) !&mcp.Server {
|
||||
logger.info('Creating new Baobab MCP server')
|
||||
|
||||
// Initialize the server with the empty handlers map
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'generate_module_from_openapi': generate_module_from_openapi_tool
|
||||
'generate_methods_file': generate_methods_file_tool
|
||||
'generate_methods_interface_file': generate_methods_interface_file_tool
|
||||
'generate_model_file': generate_model_file_tool
|
||||
'generate_methods_example_file': generate_methods_example_file_tool
|
||||
}
|
||||
tool_handlers: {
|
||||
'generate_module_from_openapi': v.generate_module_from_openapi_tool_handler
|
||||
'generate_methods_file': v.generate_methods_file_tool_handler
|
||||
'generate_methods_interface_file': v.generate_methods_interface_file_tool_handler
|
||||
'generate_model_file': v.generate_model_file_tool_handler
|
||||
'generate_methods_example_file': v.generate_methods_example_file_tool_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'baobab'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})!
|
||||
return server
|
||||
}
|
||||
2
lib/ai/mcp/pugconvert/cmd/.gitignore
vendored
2
lib/ai/mcp/pugconvert/cmd/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
main
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
export name="mcp_pugconvert"
|
||||
|
||||
# Change to the directory containing this script
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Compile the V program
|
||||
v -n -w -gc none -cc tcc -d use_openssl -enable-globals main.v
|
||||
|
||||
# Ensure the binary is executable
|
||||
chmod +x main
|
||||
mv main ~/hero/bin/${name}
|
||||
|
||||
echo "Compilation successful. Binary '${name}' is ready."
|
||||
@@ -1,17 +0,0 @@
|
||||
module main
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp.servers.pugconvert.mcp
|
||||
|
||||
fn main() {
|
||||
// Create a new MCP server
|
||||
mut server := mcp.new_mcp_server() or {
|
||||
eprintln('Failed to create MCP server: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Start the server
|
||||
server.start() or {
|
||||
eprintln('Failed to start MCP server: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
module pugconvert
|
||||
|
||||
import freeflowuniverse.herolib.clients.openai
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import json
|
||||
|
||||
pub fn convert_pug(mydir string) ! {
|
||||
mut d := pathlib.get_dir(path: mydir, create: false)!
|
||||
list := d.list(regex: [r'.*\.pug$'], include_links: false, files_only: true)!
|
||||
for item in list.paths {
|
||||
convert_pug_file(item.path)!
|
||||
}
|
||||
}
|
||||
|
||||
// extract_template parses AI response content to extract just the template
|
||||
fn extract_template(raw_content string) string {
|
||||
mut content := raw_content
|
||||
|
||||
// First check for </think> tag
|
||||
if content.contains('</think>') {
|
||||
content = content.split('</think>')[1].trim_space()
|
||||
}
|
||||
|
||||
// Look for ```jet code block
|
||||
if content.contains('```jet') {
|
||||
parts := content.split('```jet')
|
||||
if parts.len > 1 {
|
||||
end_parts := parts[1].split('```')
|
||||
if end_parts.len > 0 {
|
||||
content = end_parts[0].trim_space()
|
||||
}
|
||||
}
|
||||
} else if content.contains('```') {
|
||||
// If no ```jet, look for regular ``` code block
|
||||
parts := content.split('```')
|
||||
if parts.len >= 2 {
|
||||
// Take the content between the first set of ```
|
||||
// This handles both ```content``` and cases where there's only an opening ```
|
||||
content = parts[1].trim_space()
|
||||
|
||||
// If we only see an opening ``` but no closing, cleanup any remaining backticks
|
||||
// to avoid incomplete formatting markers
|
||||
if !content.contains('```') {
|
||||
content = content.replace('`', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
pub fn convert_pug_file(myfile string) ! {
|
||||
println(myfile)
|
||||
|
||||
// Create new file path by replacing .pug extension with .jet
|
||||
jet_file := myfile.replace('.pug', '.jet')
|
||||
|
||||
// Check if jet file already exists, if so skip processing
|
||||
mut jet_path_exist := pathlib.get_file(path: jet_file, create: false)!
|
||||
if jet_path_exist.exists() {
|
||||
println('Jet file already exists: ${jet_file}. Skipping conversion.')
|
||||
return
|
||||
}
|
||||
|
||||
mut content_path := pathlib.get_file(path: myfile, create: false)!
|
||||
content := content_path.read()!
|
||||
|
||||
mut l := loader()
|
||||
mut client := openai.get()!
|
||||
|
||||
base_instruction := '
|
||||
You are a template language converter. You convert Pug templates to Jet templates.
|
||||
|
||||
The target template language, Jet, is defined as follows:
|
||||
'
|
||||
|
||||
base_user_prompt := '
|
||||
Convert this following Pug template to Jet:
|
||||
|
||||
only output the resulting template, no explanation, no steps, just the jet template
|
||||
'
|
||||
|
||||
// We'll retry up to 5 times if validation fails
|
||||
max_attempts := 5
|
||||
mut attempts := 0
|
||||
mut is_valid := false
|
||||
mut error_message := ''
|
||||
mut template := ''
|
||||
|
||||
for attempts < max_attempts && !is_valid {
|
||||
attempts++
|
||||
|
||||
mut system_content := texttools.dedent(base_instruction) + '\n' + l.jet()
|
||||
mut user_prompt := ''
|
||||
|
||||
// Create different prompts for first attempt vs retries
|
||||
if attempts == 1 {
|
||||
// First attempt - convert from PUG
|
||||
user_prompt = texttools.dedent(base_user_prompt) + '\n' + content
|
||||
|
||||
// Print what we're sending to the AI service
|
||||
println('Sending to OpenAI for conversion:')
|
||||
println('--------------------------------')
|
||||
println(content)
|
||||
println('--------------------------------')
|
||||
} else {
|
||||
// Retries - focus on fixing the previous errors
|
||||
println('Attempt ${attempts}: Retrying with error feedback')
|
||||
user_prompt = '
|
||||
The previous Jet template conversion had the following error:
|
||||
ERROR: ${error_message}
|
||||
|
||||
Here was the template that had errors:
|
||||
```
|
||||
${template}
|
||||
```
|
||||
|
||||
The original pug input was was
|
||||
```
|
||||
${content}
|
||||
```
|
||||
|
||||
Please fix the template and try again. Learn from feedback and check which jet template was created.
|
||||
Return only the corrected Jet template.
|
||||
Dont send back more information than the fixed template, make sure its in jet format.
|
||||
|
||||
' // Print what we're sending for the retry
|
||||
|
||||
println('Sending to OpenAI for correction:')
|
||||
println('--------------------------------')
|
||||
println(user_prompt)
|
||||
println('--------------------------------')
|
||||
}
|
||||
|
||||
mut m := openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .system
|
||||
content: system_content
|
||||
},
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: user_prompt
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Create a chat completion request
|
||||
res := client.chat_completion(
|
||||
msgs: m
|
||||
model: 'deepseek-r1-distill-llama-70b'
|
||||
max_completion_tokens: 64000
|
||||
)!
|
||||
|
||||
println('-----')
|
||||
|
||||
// Print AI response before extraction
|
||||
println('Response received from AI:')
|
||||
println('--------------------------------')
|
||||
println(res.choices[0].message.content)
|
||||
println('--------------------------------')
|
||||
|
||||
// Extract the template from the AI response
|
||||
template = extract_template(res.choices[0].message.content)
|
||||
|
||||
println('Extracted template for ${myfile}:')
|
||||
println('--------------------------------')
|
||||
println(template)
|
||||
println('--------------------------------')
|
||||
|
||||
// Validate the template
|
||||
validation_result := jetvaliditycheck(template) or {
|
||||
// If validation service is unavailable, we'll just proceed with the template
|
||||
println('Warning: Template validation service unavailable: ${err}')
|
||||
break
|
||||
}
|
||||
|
||||
// Check if template is valid
|
||||
if validation_result.is_valid {
|
||||
is_valid = true
|
||||
println('Template validation successful!')
|
||||
} else {
|
||||
error_message = validation_result.error
|
||||
println('Template validation failed: ${error_message}')
|
||||
}
|
||||
}
|
||||
|
||||
// Report the validation outcome
|
||||
if is_valid {
|
||||
println('Successfully converted template after ${attempts} attempt(s)')
|
||||
// Create the file and write the processed content
|
||||
println('Converted to: ${jet_file}')
|
||||
mut jet_path := pathlib.get_file(path: jet_file, create: true)!
|
||||
jet_path.write(template)!
|
||||
} else if attempts >= max_attempts {
|
||||
println('Warning: Could not validate template after ${max_attempts} attempts')
|
||||
println('Using best attempt despite validation errors: ${error_message}')
|
||||
jet_file2 := jet_file.replace('.jet', '_error.jet')
|
||||
mut jet_path2 := pathlib.get_file(path: jet_file2, create: true)!
|
||||
jet_path2.write(template)!
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
module pugconvert
|
||||
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import json
|
||||
|
||||
// JetTemplateResponse is the expected response structure from the validation service
|
||||
struct JetTemplateResponse {
|
||||
valid bool
|
||||
message string
|
||||
error string
|
||||
}
|
||||
|
||||
// ValidationResult represents the result of a template validation
|
||||
pub struct ValidationResult {
|
||||
pub:
|
||||
is_valid bool
|
||||
error string
|
||||
}
|
||||
|
||||
// jetvaliditycheck validates a Jet template by sending it to a validation service
|
||||
// The function sends the template to http://localhost:9020/checkjet for validation
|
||||
// Returns a ValidationResult containing validity status and any error messages
|
||||
pub fn jetvaliditycheck(jetcontent string) !ValidationResult {
|
||||
// Create HTTP connection to the validation service
|
||||
mut conn := httpconnection.HTTPConnection{
|
||||
base_url: 'http://localhost:9020'
|
||||
}
|
||||
|
||||
// Prepare the request data - template content wrapped in JSON
|
||||
template_data := json.encode({
|
||||
'template': jetcontent
|
||||
})
|
||||
|
||||
// Print what we're sending to the AI service
|
||||
// println('Sending to JET validation service:')
|
||||
// println('--------------------------------')
|
||||
// println(jetcontent)
|
||||
// println('--------------------------------')
|
||||
|
||||
// Send the POST request to the validation endpoint
|
||||
req := httpconnection.Request{
|
||||
prefix: 'checkjet'
|
||||
data: template_data
|
||||
dataformat: .json
|
||||
}
|
||||
|
||||
// Execute the request
|
||||
result := conn.post_json_str(req) or {
|
||||
// Handle connection errors
|
||||
return ValidationResult{
|
||||
is_valid: false
|
||||
error: 'Connection error: ${err}'
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to parse the response as JSON using the expected struct
|
||||
response := json.decode(JetTemplateResponse, result) or {
|
||||
// If we can't parse JSON using our struct, the server didn't return the expected format
|
||||
return ValidationResult{
|
||||
is_valid: false
|
||||
error: 'Server returned unexpected format: ${err.msg()}'
|
||||
}
|
||||
}
|
||||
|
||||
// Use the structured response data
|
||||
if response.valid == false {
|
||||
error_msg := if response.error != '' {
|
||||
response.error
|
||||
} else if response.message != '' {
|
||||
response.message
|
||||
} else {
|
||||
'Unknown validation error'
|
||||
}
|
||||
|
||||
return ValidationResult{
|
||||
is_valid: false
|
||||
error: error_msg
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult{
|
||||
is_valid: true
|
||||
error: ''
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
module pugconvert
|
||||
|
||||
import v.embed_file
|
||||
import os
|
||||
|
||||
@[heap]
|
||||
pub struct FileLoader {
|
||||
pub mut:
|
||||
embedded_files map[string]embed_file.EmbedFileData @[skip; str: skip]
|
||||
}
|
||||
|
||||
fn (mut loader FileLoader) load() {
|
||||
loader.embedded_files['jet'] = $embed_file('templates/jet_instructions.md')
|
||||
}
|
||||
|
||||
fn (mut loader FileLoader) jet() string {
|
||||
c := loader.embedded_files['jet'] or { panic('bug embed') }
|
||||
return c.to_string()
|
||||
}
|
||||
|
||||
fn loader() FileLoader {
|
||||
mut loader := FileLoader{}
|
||||
loader.load()
|
||||
return loader
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
# Jet Template Engine Syntax Reference
|
||||
|
||||
## Delimiters
|
||||
|
||||
Template delimiters are `{{` and `}}`.
|
||||
Delimiters can use `.` to output the execution context:
|
||||
|
||||
```jet
|
||||
hello {{ . }} <!-- context = "world" => "hello world" -->
|
||||
```
|
||||
|
||||
### Whitespace Trimming
|
||||
|
||||
Whitespace around delimiters can be trimmed using `{{-` and `-}}`:
|
||||
|
||||
```jet
|
||||
foo {{- "bar" -}} baz <!-- outputs "foobarbaz" -->
|
||||
```
|
||||
|
||||
Whitespace includes spaces, tabs, carriage returns, and newlines.
|
||||
|
||||
### Comments
|
||||
|
||||
Comments use `{* ... *}`:
|
||||
|
||||
```jet
|
||||
{* this is a comment *}
|
||||
|
||||
{*
|
||||
Multiline
|
||||
{{ expressions }} are ignored
|
||||
*}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variables
|
||||
|
||||
### Initialization
|
||||
|
||||
```jet
|
||||
{{ foo := "bar" }}
|
||||
```
|
||||
|
||||
### Assignment
|
||||
|
||||
```jet
|
||||
{{ foo = "asd" }}
|
||||
{{ foo = 4711 }}
|
||||
```
|
||||
|
||||
Skip assignment but still evaluate:
|
||||
|
||||
```jet
|
||||
{{ _ := stillRuns() }}
|
||||
{{ _ = stillRuns() }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expressions
|
||||
|
||||
### Identifiers
|
||||
|
||||
Identifiers resolve to values:
|
||||
|
||||
```jet
|
||||
{{ len("hello") }}
|
||||
{{ isset(foo, bar) }}
|
||||
```
|
||||
|
||||
### Indexing
|
||||
|
||||
#### String
|
||||
|
||||
```jet
|
||||
{{ s := "helloworld" }}
|
||||
{{ s[1] }} <!-- 101 (ASCII of 'e') -->
|
||||
```
|
||||
|
||||
#### Slice / Array
|
||||
|
||||
```jet
|
||||
{{ s := slice("foo", "bar", "asd") }}
|
||||
{{ s[0] }}
|
||||
{{ s[2] }}
|
||||
```
|
||||
|
||||
#### Map
|
||||
|
||||
```jet
|
||||
{{ m := map("foo", 123, "bar", 456) }}
|
||||
{{ m["foo"] }}
|
||||
```
|
||||
|
||||
#### Struct
|
||||
|
||||
```jet
|
||||
{{ user["Name"] }}
|
||||
```
|
||||
|
||||
### Field Access
|
||||
|
||||
#### Map
|
||||
|
||||
```jet
|
||||
{{ m.foo }}
|
||||
{{ range s }}
|
||||
{{ .foo }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Struct
|
||||
|
||||
```jet
|
||||
{{ user.Name }}
|
||||
{{ range users }}
|
||||
{{ .Name }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Slicing
|
||||
|
||||
```jet
|
||||
{{ s := slice(6, 7, 8, 9, 10, 11) }}
|
||||
{{ sevenEightNine := s[1:4] }}
|
||||
```
|
||||
|
||||
### Arithmetic
|
||||
|
||||
```jet
|
||||
{{ 1 + 2 * 3 - 4 }}
|
||||
{{ (1 + 2) * 3 - 4.1 }}
|
||||
```
|
||||
|
||||
### String Concatenation
|
||||
|
||||
```jet
|
||||
{{ "HELLO" + " " + "WORLD!" }}
|
||||
```
|
||||
|
||||
#### Logical Operators
|
||||
|
||||
- `&&`
|
||||
- `||`
|
||||
- `!`
|
||||
- `==`, `!=`
|
||||
- `<`, `>`, `<=`, `>=`
|
||||
|
||||
```jet
|
||||
{{ item == true || !item2 && item3 != "test" }}
|
||||
{{ item >= 12.5 || item < 6 }}
|
||||
```
|
||||
|
||||
### Ternary Operator
|
||||
|
||||
```jet
|
||||
<title>{{ .HasTitle ? .Title : "Title not set" }}</title>
|
||||
```
|
||||
|
||||
### Method Calls
|
||||
|
||||
```jet
|
||||
{{ user.Rename("Peter") }}
|
||||
{{ range users }}
|
||||
{{ .FullName() }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Function Calls
|
||||
|
||||
```jet
|
||||
{{ len(s) }}
|
||||
{{ isset(foo, bar) }}
|
||||
```
|
||||
|
||||
#### Prefix Syntax
|
||||
|
||||
```jet
|
||||
{{ len: s }}
|
||||
{{ isset: foo, bar }}
|
||||
```
|
||||
|
||||
#### Pipelining
|
||||
|
||||
```jet
|
||||
{{ "123" | len }}
|
||||
{{ "FOO" | lower | len }}
|
||||
{{ "hello" | repeat: 2 | len }}
|
||||
```
|
||||
|
||||
**Escapers must be last in a pipeline:**
|
||||
|
||||
```jet
|
||||
{{ "hello" | upper | raw }} <!-- valid -->
|
||||
{{ raw: "hello" }} <!-- valid -->
|
||||
{{ raw: "hello" | upper }} <!-- invalid -->
|
||||
```
|
||||
|
||||
#### Piped Argument Slot
|
||||
|
||||
```jet
|
||||
{{ 2 | repeat("foo", _) }}
|
||||
{{ 2 | repeat("foo", _) | repeat(_, 3) }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Control Structures
|
||||
|
||||
### if
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
foo is 'asd'!
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
...
|
||||
{{ else }}
|
||||
...
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else if
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
{{ else if foo == 4711 }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else if / else
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
{{ else if foo == 4711 }}
|
||||
{{ else }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### range
|
||||
|
||||
#### Slices / Arrays
|
||||
|
||||
```jet
|
||||
{{ range s }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range i := s }}
|
||||
{{ i }}: {{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range i, v := s }}
|
||||
{{ i }}: {{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Maps
|
||||
|
||||
```jet
|
||||
{{ range k := m }}
|
||||
{{ k }}: {{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range k, v := m }}
|
||||
{{ k }}: {{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Channels
|
||||
|
||||
```jet
|
||||
{{ range v := c }}
|
||||
{{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Custom Ranger
|
||||
|
||||
Any Go type implementing `Ranger` can be ranged over.
|
||||
|
||||
#### else
|
||||
|
||||
```jet
|
||||
{{ range searchResults }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
No results found :(
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### try
|
||||
|
||||
```jet
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### try / catch
|
||||
|
||||
```jet
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ catch }}
|
||||
Fallback content
|
||||
{{ end }}
|
||||
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ catch err }}
|
||||
{{ log(err.Error()) }}
|
||||
Error: {{ err.Error() }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
### include
|
||||
|
||||
```jet
|
||||
{{ include "./user.jet" }}
|
||||
|
||||
<!-- user.jet -->
|
||||
<div class="user">
|
||||
{{ .["name"] }}: {{ .["email"] }}
|
||||
</div>
|
||||
```
|
||||
|
||||
### return
|
||||
|
||||
```jet
|
||||
<!-- foo.jet -->
|
||||
{{ return "foo" }}
|
||||
|
||||
<!-- bar.jet -->
|
||||
{{ foo := exec("./foo.jet") }}
|
||||
Hello, {{ foo }}!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Blocks
|
||||
|
||||
### block
|
||||
|
||||
```jet
|
||||
{{ block copyright() }}
|
||||
<div>© ACME, Inc. 2020</div>
|
||||
{{ end }}
|
||||
|
||||
{{ block inputField(type="text", label, id, value="", required=false) }}
|
||||
<label for="{{ id }}">{{ label }}</label>
|
||||
<input type="{{ type }}" value="{{ value }}" id="{{ id }}" {{ required ? "required" : "" }} />
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### yield
|
||||
|
||||
```jet
|
||||
{{ yield copyright() }}
|
||||
|
||||
{{ yield inputField(id="firstname", label="First name", required=true) }}
|
||||
|
||||
{{ block buff() }}
|
||||
<strong>{{ . }}</strong>
|
||||
{{ end }}
|
||||
|
||||
{{ yield buff() "Batman" }}
|
||||
```
|
||||
|
||||
### content
|
||||
|
||||
```jet
|
||||
{{ block link(target) }}
|
||||
<a href="{{ target }}">{{ yield content }}</a>
|
||||
{{ end }}
|
||||
|
||||
{{ yield link(target="https://example.com") content }}
|
||||
Example Inc.
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
```jet
|
||||
{{ block header() }}
|
||||
<div class="header">
|
||||
{{ yield content }}
|
||||
</div>
|
||||
{{ content }}
|
||||
<h1>Hey {{ name }}!</h1>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Recursion
|
||||
|
||||
```jet
|
||||
{{ block menu() }}
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>{{ .Text }}{{ if len(.Children) }}{{ yield menu() .Children }}{{ end }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### extends
|
||||
|
||||
```jet
|
||||
<!-- content.jet -->
|
||||
{{ extends "./layout.jet" }}
|
||||
{{ block body() }}
|
||||
<main>This content can be yielded anywhere.</main>
|
||||
{{ end }}
|
||||
|
||||
<!-- layout.jet -->
|
||||
<html>
|
||||
<body>
|
||||
{{ yield body() }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### import
|
||||
|
||||
```jet
|
||||
<!-- my_blocks.jet -->
|
||||
{{ block body() }}
|
||||
<main>This content can be yielded anywhere.</main>
|
||||
{{ end }}
|
||||
|
||||
<!-- index.jet -->
|
||||
{{ import "./my_blocks.jet" }}
|
||||
<html>
|
||||
<body>
|
||||
{{ yield body() }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json { Any }
|
||||
import freeflowuniverse.herolib.ai.mcp.aitools.pugconvert
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
|
||||
pub fn handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
path := arguments['path'].str()
|
||||
|
||||
// Check if path exists
|
||||
if !os.exists(path) {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if path is a file or directory
|
||||
is_directory := os.is_dir(path)
|
||||
|
||||
mut message := ''
|
||||
|
||||
if is_directory {
|
||||
// Convert all pug files in the directory
|
||||
pugconvert.convert_pug(path) or {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]('Error converting pug files in directory: ${err}')
|
||||
}
|
||||
}
|
||||
message = "Successfully converted all pug files in directory '${path}'"
|
||||
} else if path.ends_with('.pug') {
|
||||
// Convert a single pug file
|
||||
pugconvert.convert_pug_file(path) or {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]('Error converting pug file: ${err}')
|
||||
}
|
||||
}
|
||||
message = "Successfully converted pug file '${path}'"
|
||||
} else {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' is not a directory or .pug file")
|
||||
}
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](message)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.ai.mcp.logger
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
pub fn new_mcp_server() !&mcp.Server {
|
||||
logger.info('Creating new Developer MCP server')
|
||||
|
||||
// Initialize the server with the empty handlers map
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'pugconvert': specs
|
||||
}
|
||||
tool_handlers: {
|
||||
'pugconvert': handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'developer'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})!
|
||||
return server
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.ai.mcp.logger
|
||||
|
||||
const specs = mcp.Tool{
|
||||
name: 'pugconvert'
|
||||
description: 'Convert Pug template files to Jet template files'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to a .pug file or directory containing .pug files to convert'
|
||||
})
|
||||
}
|
||||
required: ['path']
|
||||
}
|
||||
}
|
||||
2
lib/ai/mcp/rhai/cmd/.gitignore
vendored
2
lib/ai/mcp/rhai/cmd/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
main
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
export name="mcp_rhai"
|
||||
|
||||
# Change to the directory containing this script
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Compile the V program
|
||||
v -n -w -gc none -cc tcc -d use_openssl -enable-globals main.v
|
||||
|
||||
# Ensure the binary is executable
|
||||
chmod +x main
|
||||
mv main ~/hero/bin/${name}
|
||||
|
||||
echo "Compilation successful. Binary '${name}' is ready."
|
||||
@@ -1,18 +0,0 @@
|
||||
module main
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp.rhai.mcp
|
||||
import log
|
||||
|
||||
fn main() {
|
||||
// Create a new MCP server
|
||||
mut server := mcp.new_mcp_server() or {
|
||||
log.error('Failed to create MCP server: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Start the server
|
||||
server.start() or {
|
||||
log.error('Failed to start MCP server: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,532 +0,0 @@
|
||||
#!/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
|
||||
current_dir := os.dir(@FILE)
|
||||
|
||||
// Check if a source code path was provided as an argument
|
||||
if os.args.len < 2 {
|
||||
println('Please provide the path to the source code directory as an argument')
|
||||
println('Example: ./example.vsh /path/to/source/code/directory')
|
||||
return
|
||||
}
|
||||
|
||||
// Get the source code path from the command line arguments
|
||||
source_code_path := os.args[1]
|
||||
|
||||
// Check if the path exists and is a directory
|
||||
if !os.exists(source_code_path) {
|
||||
println('Source code path does not exist: ${source_code_path}')
|
||||
return
|
||||
}
|
||||
|
||||
if !os.is_dir(source_code_path) {
|
||||
println('Source code path is not a directory: ${source_code_path}')
|
||||
return
|
||||
}
|
||||
|
||||
// Get all Rust files in the directory
|
||||
files := os.ls(source_code_path) or {
|
||||
println('Failed to list files in directory: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// 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 == '' {
|
||||
println('No Rust files found in directory: ${source_code_path}')
|
||||
return
|
||||
}
|
||||
|
||||
// Read the rhaiwrapping.md file
|
||||
rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping.md') or {
|
||||
println('Failed to read rhaiwrapping.md: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
// Determine the crate path from the source code path
|
||||
// Extract the path relative to the src directory
|
||||
src_index := source_code_path.index('src/') or {
|
||||
println('Could not determine crate path: src/ not found in path')
|
||||
return
|
||||
}
|
||||
|
||||
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('::')
|
||||
crate_path := 'sal::${rel_path}'
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Extract the module name from the directory path (last component)
|
||||
dir_parts := source_code_path.split('/')
|
||||
name := dir_parts[dir_parts.len - 1]
|
||||
|
||||
// Create the prompt with source code, wrapper example, and rhai_wrapping_md
|
||||
prompt_content := create_rhai_wrappers(name, source_code, os.read_file('${current_dir}/prompts/example_script.md') or {
|
||||
''
|
||||
}, os.read_file('${current_dir}/prompts/wrapper.md') or { '' }, os.read_file('${current_dir}/prompts/errors.md') or {
|
||||
''
|
||||
}, crate_path)
|
||||
|
||||
// Create a prompt function that returns the prepared content
|
||||
prompt_function := fn [prompt_content] (input string) string {
|
||||
return prompt_content
|
||||
}
|
||||
|
||||
gen := RhaiGen{
|
||||
name: name
|
||||
dir: source_code_path
|
||||
}
|
||||
|
||||
// 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
|
||||
result := task.initiate('') 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.')
|
||||
}
|
||||
|
||||
// Define the prompt functions
|
||||
fn separate_functions(input string) string {
|
||||
return 'Read the following Rust code and separate it into functions. Identify all the methods in the Container implementation and their purposes.\n\n${input}'
|
||||
}
|
||||
|
||||
fn create_wrappers(input string) string {
|
||||
return 'Create Rhai wrappers for the Rust functions identified in the previous step. The wrappers should follow the builder pattern and provide a clean API for use in Rhai scripts. Include error handling and type conversion.\n\n${input}'
|
||||
}
|
||||
|
||||
fn create_example(input string) string {
|
||||
return 'Create a Rhai example script that demonstrates how to use the wrapper functions. The example should be based on the provided example.rs file but adapted for Rhai syntax. Create a web server example that uses the container functions.\n\n${input}'
|
||||
}
|
||||
|
||||
// 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 {
|
||||
guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
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::<name>`
|
||||
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 ''
|
||||
}
|
||||
|
||||
struct RhaiGen {
|
||||
name string
|
||||
dir string
|
||||
}
|
||||
|
||||
// Define the callback function that processes the response and compiles the code
|
||||
fn (gen RhaiGen) process_rhai_wrappers(response string) !string {
|
||||
// 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', '')
|
||||
// if engine_rs_content == '' {
|
||||
// // Use the template engine.rs
|
||||
// engine_rs_content = $tmpl('./templates/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_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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract function names from the wrapper.rs content
|
||||
functions := extract_functions_from_code(wrapper_rs_content)
|
||||
|
||||
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: wrapper_rs_content
|
||||
example_rs: $tmpl('./templates/example.rs')
|
||||
engine_rs: engine_rs_content
|
||||
generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs')
|
||||
cargo_toml: $tmpl('./templates/cargo.toml')
|
||||
example_rhai: example_rhai_content
|
||||
}
|
||||
|
||||
// Create the wrapper module
|
||||
base_target_dir := gen.dir
|
||||
project_dir := create_wrapper_module(wrapper, functions, gen.name, base_target_dir) or {
|
||||
return error('Failed to create wrapper module: ${err}')
|
||||
}
|
||||
|
||||
// Run the example
|
||||
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 'Successfully generated Rhai wrappers and ran the example!\n\nProject created at: ${project_dir}\n\nBuild output:\n${build_result.output}\n\nRun output:\n${run_result.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
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
#!/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
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
module logic
|
||||
|
||||
import freeflowuniverse.herolib.ai.escalayer
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import freeflowuniverse.herolib.ai.utils
|
||||
import os
|
||||
|
||||
pub fn generate_rhai_wrapper(name string, source_path string) !string {
|
||||
// Detect source package and module information
|
||||
source_pkg_info := rust.detect_source_package(source_path)!
|
||||
source_code := rust.read_source_code(source_path)!
|
||||
prompt := rhai_wrapper_generation_prompt(name, source_code, source_pkg_info)!
|
||||
return run_wrapper_generation_task(prompt, RhaiGen{
|
||||
name: name
|
||||
dir: source_path
|
||||
source_pkg_info: source_pkg_info
|
||||
})!
|
||||
}
|
||||
|
||||
// Runs the task to generate Rhai wrappers
|
||||
pub 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
|
||||
pub fn rhai_wrapper_generation_prompt(name string, source_code string, source_pkg_info rust.SourcePackageInfo) !string {
|
||||
current_dir := os.dir(@FILE)
|
||||
example_rhai := os.read_file('${current_dir}/prompts/example_script.md')!
|
||||
wrapper_md := os.read_file('${current_dir}/prompts/wrapper.md')!
|
||||
errors_md := os.read_file('${current_dir}/prompts/errors.md')!
|
||||
|
||||
// Load all required template and guide files
|
||||
guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')!
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')!
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
|
||||
prompt := $tmpl('./prompts/main.md')
|
||||
return prompt
|
||||
}
|
||||
|
||||
@[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
|
||||
pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string) !string {
|
||||
// Define project directory paths
|
||||
project_dir := '${path}/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(path) 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}')
|
||||
}
|
||||
} else {
|
||||
// Use default lib.rs template if none provided
|
||||
lib_rs_content := $tmpl('./templates/lib.rs')
|
||||
os.write_file('${project_dir}/src/lib.rs', lib_rs_content) 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}')
|
||||
}
|
||||
} else {
|
||||
// Use default example.rs template if none provided
|
||||
example_rs_content := $tmpl('./templates/example.rs')
|
||||
os.write_file('${examples_dir}/example.rs', example_rs_content) 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
|
||||
os.write_file('${project_dir}/Cargo.toml', wrapper.cargo_toml) or {
|
||||
return error('Failed to write Cargo.toml: ${err}')
|
||||
}
|
||||
|
||||
// Write the example.rhai file
|
||||
os.write_file('${examples_dir}/example.rhai', wrapper.example_rhai) or {
|
||||
return error('Failed to write example.rhai: ${err}')
|
||||
}
|
||||
|
||||
return project_dir
|
||||
}
|
||||
|
||||
// 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
|
||||
source_pkg_info rust.SourcePackageInfo
|
||||
}
|
||||
|
||||
// Process the AI response and compile the generated code
|
||||
pub fn (gen RhaiGen) process_rhai_wrappers(input string) !string {
|
||||
blocks := extract_code_blocks(input)!
|
||||
source_pkg_info := gen.source_pkg_info
|
||||
// Create the module structure
|
||||
mod := WrapperModule{
|
||||
lib_rs: blocks.lib_rs
|
||||
engine_rs: blocks.engine_rs
|
||||
example_rhai: blocks.example_rhai
|
||||
generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs')
|
||||
wrapper_rs: blocks.wrapper_rs
|
||||
}
|
||||
|
||||
// Write the module files
|
||||
project_dir := write_rhai_wrapper_module(mod, gen.name, gen.dir)!
|
||||
|
||||
return project_dir
|
||||
}
|
||||
|
||||
// CodeBlocks struct to hold extracted code blocks
|
||||
struct CodeBlocks {
|
||||
wrapper_rs string
|
||||
engine_rs string
|
||||
example_rhai string
|
||||
lib_rs string
|
||||
}
|
||||
|
||||
// Extract code blocks from the AI response
|
||||
fn extract_code_blocks(response string) !CodeBlocks {
|
||||
// Extract wrapper.rs content
|
||||
wrapper_rs_content := utils.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 := utils.extract_code_block(response, 'engine.rs', 'rust')
|
||||
if engine_rs_content == '' {
|
||||
// Try to extract from the response without explicit language marker
|
||||
engine_rs_content = utils.extract_code_block(response, 'engine.rs', '')
|
||||
}
|
||||
|
||||
// Extract example.rhai content
|
||||
mut example_rhai_content := utils.extract_code_block(response, 'example.rhai', 'rhai')
|
||||
if example_rhai_content == '' {
|
||||
// Try to extract from the response without explicit language marker
|
||||
example_rhai_content = utils.extract_code_block(response, 'example.rhai', '')
|
||||
if example_rhai_content == '' {
|
||||
return error('Failed to extract example.rhai content from response. Please ensure your code is properly formatted inside a code block that starts with ```rhai\n// example.rhai and ends with ```')
|
||||
}
|
||||
}
|
||||
|
||||
// Extract lib.rs content
|
||||
lib_rs_content := utils.extract_code_block(response, 'lib.rs', 'rust')
|
||||
if lib_rs_content == '' {
|
||||
return error('Failed to extract lib.rs content from response. Please ensure your code is properly formatted inside a code block that starts with ```rust\n// lib.rs and ends with ```')
|
||||
}
|
||||
|
||||
return CodeBlocks{
|
||||
wrapper_rs: wrapper_rs_content
|
||||
engine_rs: engine_rs_content
|
||||
example_rhai: example_rhai_content
|
||||
lib_rs: lib_rs_content
|
||||
}
|
||||
}
|
||||
|
||||
// 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}'
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
module logic
|
||||
|
||||
import freeflowuniverse.herolib.ai.escalayer
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import freeflowuniverse.herolib.ai.utils
|
||||
import os
|
||||
|
||||
// pub fn generate_rhai_wrapper_sampling(name string, source_path string) !string {
|
||||
// prompt := rhai_wrapper_generation_prompt(name, source_path) or {panic(err)}
|
||||
// return run_wrapper_generation_task_sampling(prompt, RhaiGen{
|
||||
// name: name
|
||||
// dir: source_path
|
||||
// }) or {panic(err)}
|
||||
// }
|
||||
|
||||
// // Runs the task to generate Rhai wrappers
|
||||
// pub fn run_wrapper_generation_task_sampling(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('')
|
||||
// }
|
||||
|
||||
// @[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
|
||||
// pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string)! string {
|
||||
|
||||
// // Define project directory paths
|
||||
// project_dir := '${path}/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(path) 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
|
||||
// os.write_file('${project_dir}/Cargo.toml', wrapper.cargo_toml) or {
|
||||
// return error('Failed to write Cargo.toml: ${err}')
|
||||
// }
|
||||
|
||||
// // Write the example.rhai file
|
||||
// os.write_file('${examples_dir}/example.rhai', wrapper.example_rhai) or {
|
||||
// return error('Failed to write example.rhai: ${err}')
|
||||
// }
|
||||
|
||||
// return project_dir
|
||||
// }
|
||||
|
||||
// // 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
|
||||
// }
|
||||
|
||||
// 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 := write_rhai_wrapper_module(wrapper, gen.name, gen.dir) or {
|
||||
// return error('Failed to create wrapper module: ${err}')
|
||||
// }
|
||||
|
||||
// // Build and run the project
|
||||
// build_output, run_output := rust.run_example(project_dir, 'example') 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 := utils.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 := utils.extract_code_block(response, 'engine.rs', 'rust')
|
||||
// if engine_rs_content == '' {
|
||||
// // Try to extract from the response without explicit language marker
|
||||
// engine_rs_content = utils.extract_code_block(response, 'engine.rs', '')
|
||||
// }
|
||||
|
||||
// // Extract example.rhai content
|
||||
// mut example_rhai_content := utils.extract_code_block(response, 'example.rhai', 'rhai')
|
||||
// if example_rhai_content == '' {
|
||||
// // Try to extract from the response without explicit language marker
|
||||
// example_rhai_content = utils.extract_code_block(response, 'example.rhai', '')
|
||||
// if example_rhai_content == '' {
|
||||
// return error('Failed to extract example.rhai content from response. Please ensure your code is properly formatted inside a code block that starts with ```rhai\n// example.rhai and ends with ```')
|
||||
// }
|
||||
// }
|
||||
|
||||
// return CodeBlocks{
|
||||
// wrapper_rs: wrapper_rs_content
|
||||
// engine_rs: engine_rs_content
|
||||
// example_rhai: example_rhai_content
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 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}'
|
||||
// }
|
||||
@@ -1,125 +0,0 @@
|
||||
|
||||
# Engine
|
||||
|
||||
Here is an example of a well-implemented Rhai engine for the Git module:
|
||||
|
||||
## Example engine
|
||||
|
||||
```rust
|
||||
// engine.rs
|
||||
|
||||
/// Register Nerdctl module functions with the Rhai engine
|
||||
pub fn create_rhai_engine() -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
register_nerdctl_module(&mut engine)?;
|
||||
register_nerdctl_types(&mut engine)?;
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register Container constructor
|
||||
engine.register_fn("nerdctl_container_new", container_new);
|
||||
engine.register_fn("nerdctl_container_from_image", container_from_image);
|
||||
|
||||
// Register Container instance methods
|
||||
engine.register_fn("reset", container_reset);
|
||||
engine.register_fn("with_port", container_with_port);
|
||||
engine.register_fn("with_volume", container_with_volume);
|
||||
engine.register_fn("with_env", container_with_env);
|
||||
engine.register_fn("with_network", container_with_network);
|
||||
engine.register_fn("with_network_alias", container_with_network_alias);
|
||||
engine.register_fn("with_cpu_limit", container_with_cpu_limit);
|
||||
engine.register_fn("with_memory_limit", container_with_memory_limit);
|
||||
engine.register_fn("with_restart_policy", container_with_restart_policy);
|
||||
engine.register_fn("with_health_check", container_with_health_check);
|
||||
engine.register_fn("with_ports", container_with_ports);
|
||||
engine.register_fn("with_volumes", container_with_volumes);
|
||||
engine.register_fn("with_envs", container_with_envs);
|
||||
engine.register_fn("with_network_aliases", container_with_network_aliases);
|
||||
engine.register_fn("with_memory_swap_limit", container_with_memory_swap_limit);
|
||||
engine.register_fn("with_cpu_shares", container_with_cpu_shares);
|
||||
engine.register_fn("with_health_check_options", container_with_health_check_options);
|
||||
engine.register_fn("with_snapshotter", container_with_snapshotter);
|
||||
engine.register_fn("with_detach", container_with_detach);
|
||||
engine.register_fn("build", container_build);
|
||||
engine.register_fn("start", container_start);
|
||||
engine.register_fn("stop", container_stop);
|
||||
engine.register_fn("remove", container_remove);
|
||||
engine.register_fn("exec", container_exec);
|
||||
engine.register_fn("logs", container_logs);
|
||||
engine.register_fn("copy", container_copy);
|
||||
|
||||
// Register legacy container functions (for backward compatibility)
|
||||
engine.register_fn("nerdctl_run", nerdctl_run);
|
||||
engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name);
|
||||
engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port);
|
||||
engine.register_fn("new_run_options", new_run_options);
|
||||
engine.register_fn("nerdctl_exec", nerdctl_exec);
|
||||
engine.register_fn("nerdctl_copy", nerdctl_copy);
|
||||
engine.register_fn("nerdctl_stop", nerdctl_stop);
|
||||
engine.register_fn("nerdctl_remove", nerdctl_remove);
|
||||
engine.register_fn("nerdctl_list", nerdctl_list);
|
||||
engine.register_fn("nerdctl_logs", nerdctl_logs);
|
||||
|
||||
// Register image functions
|
||||
engine.register_fn("nerdctl_images", nerdctl_images);
|
||||
engine.register_fn("nerdctl_image_remove", nerdctl_image_remove);
|
||||
engine.register_fn("nerdctl_image_push", nerdctl_image_push);
|
||||
engine.register_fn("nerdctl_image_tag", nerdctl_image_tag);
|
||||
engine.register_fn("nerdctl_image_pull", nerdctl_image_pull);
|
||||
engine.register_fn("nerdctl_image_commit", nerdctl_image_commit);
|
||||
engine.register_fn("nerdctl_image_build", nerdctl_image_build);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register Nerdctl module types with the Rhai engine
|
||||
fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register Container type
|
||||
engine.register_type_with_name::<Container>("NerdctlContainer");
|
||||
|
||||
// Register getters for Container properties
|
||||
engine.register_get("name", |container: &mut Container| container.name.clone());
|
||||
engine.register_get("container_id", |container: &mut Container| {
|
||||
match &container.container_id {
|
||||
Some(id) => id.clone(),
|
||||
None => "".to_string(),
|
||||
}
|
||||
});
|
||||
engine.register_get("image", |container: &mut Container| {
|
||||
match &container.image {
|
||||
Some(img) => img.clone(),
|
||||
None => "".to_string(),
|
||||
}
|
||||
});
|
||||
engine.register_get("ports", |container: &mut Container| {
|
||||
let mut array = Array::new();
|
||||
for port in &container.ports {
|
||||
array.push(Dynamic::from(port.clone()));
|
||||
}
|
||||
array
|
||||
});
|
||||
engine.register_get("volumes", |container: &mut Container| {
|
||||
let mut array = Array::new();
|
||||
for volume in &container.volumes {
|
||||
array.push(Dynamic::from(volume.clone()));
|
||||
}
|
||||
array
|
||||
});
|
||||
engine.register_get("detach", |container: &mut Container| container.detach);
|
||||
|
||||
// Register Image type and methods
|
||||
engine.register_type_with_name::<Image>("NerdctlImage");
|
||||
|
||||
// Register getters for Image properties
|
||||
engine.register_get("id", |img: &mut Image| img.id.clone());
|
||||
engine.register_get("repository", |img: &mut Image| img.repository.clone());
|
||||
engine.register_get("tag", |img: &mut Image| img.tag.clone());
|
||||
engine.register_get("size", |img: &mut Image| img.size.clone());
|
||||
engine.register_get("created", |img: &mut Image| img.created.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -1,186 +0,0 @@
|
||||
# Common Errors in Rhai Wrappers and How to Fix Them
|
||||
|
||||
When creating Rhai wrappers for Rust functions, you might encounter several common errors. Here's how to address them:
|
||||
|
||||
## 1. `rhai_fn` Attribute Errors
|
||||
|
||||
```
|
||||
error: cannot find attribute `rhai_fn` in this scope
|
||||
```
|
||||
|
||||
**Solution**: Do not use the `#[rhai_fn]` attribute. Instead, register functions directly in the engine:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
#[rhai_fn(name = "pull_repository")]
|
||||
pub fn pull_repository(repo: &mut GitRepo) -> Dynamic { ... }
|
||||
|
||||
// CORRECT:
|
||||
pub fn pull_repository(repo: &mut GitRepo) -> Dynamic { ... }
|
||||
// Then register in engine.rs:
|
||||
engine.register_fn("pull_repository", pull_repository);
|
||||
```
|
||||
|
||||
## 2. Function Visibility Errors
|
||||
|
||||
```
|
||||
error[E0603]: function `create_rhai_engine` is private
|
||||
```
|
||||
|
||||
**Solution**: Make sure to declare functions as `pub` when they need to be accessed from other modules:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
fn create_rhai_engine() -> Engine { ... }
|
||||
|
||||
// CORRECT:
|
||||
pub fn create_rhai_engine() -> Engine { ... }
|
||||
```
|
||||
|
||||
## 3. Type Errors with String vs &str
|
||||
|
||||
```
|
||||
error[E0308]: `match` arms have incompatible types
|
||||
```
|
||||
|
||||
**Solution**: Ensure consistent return types in match arms. When one arm returns a string literal (`&str`) and another returns a `String`, convert them to be consistent:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
match r.pull() {
|
||||
Ok(_) => "Successfully pulled changes",
|
||||
Err(err) => {
|
||||
let error_msg = format!("Error pulling changes: {}", err);
|
||||
error_msg // This is a String, not matching the &str above
|
||||
}
|
||||
}
|
||||
|
||||
// CORRECT - Option 1: Convert &str to String
|
||||
match r.pull() {
|
||||
Ok(_) => String::from("Successfully pulled changes"),
|
||||
Err(err) => format!("Error pulling changes: {}", err)
|
||||
}
|
||||
|
||||
// CORRECT - Option 2: Use String::from for all string literals
|
||||
match r.pull() {
|
||||
Ok(_) => String::from("Successfully pulled changes"),
|
||||
Err(err) => {
|
||||
let error_msg = format!("Error pulling changes: {}", err);
|
||||
error_msg
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Lifetime Errors
|
||||
|
||||
```
|
||||
error: lifetime may not live long enough
|
||||
```
|
||||
|
||||
**Solution**: When returning references from closures, you need to ensure the lifetime is valid. For path operations, convert to owned strings:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
repo_clone.wrap(|r| r.path())
|
||||
|
||||
// CORRECT:
|
||||
repo_clone.wrap(|r| r.path().to_string())
|
||||
```
|
||||
|
||||
## 5. Sized Trait Errors
|
||||
|
||||
```
|
||||
error[E0277]: the size for values of type `Self` cannot be known at compilation time
|
||||
```
|
||||
|
||||
**Solution**: Add a `Sized` bound to the `Self` type in trait definitions:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
trait RhaiWrapper {
|
||||
fn wrap<F, R>(&self, f: F) -> Dynamic
|
||||
where
|
||||
F: FnOnce(Self) -> R,
|
||||
R: ToRhai;
|
||||
}
|
||||
|
||||
// CORRECT:
|
||||
trait RhaiWrapper {
|
||||
fn wrap<F, R>(&self, f: F) -> Dynamic
|
||||
where
|
||||
F: FnOnce(Self) -> R,
|
||||
R: ToRhai,
|
||||
Self: Sized;
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Unused Imports
|
||||
|
||||
```
|
||||
warning: unused imports: `Engine`, `EvalAltResult`, `FLOAT`, `INT`, and `plugin::*`
|
||||
```
|
||||
|
||||
**Solution**: Remove unused imports to clean up your code:
|
||||
|
||||
```rust
|
||||
// INCORRECT:
|
||||
use rhai::{Engine, EvalAltResult, plugin::*, FLOAT, INT, Dynamic, Map, Array};
|
||||
|
||||
// CORRECT - only keep what you use:
|
||||
use rhai::{Dynamic, Array};
|
||||
```
|
||||
|
||||
## 7. Overuse of Dynamic Types
|
||||
|
||||
```
|
||||
error[E0277]: the trait bound `Vec<Dynamic>: generic_wrapper::ToRhai` is not satisfied
|
||||
```
|
||||
|
||||
**Solution**: Use proper static typing instead of Dynamic types whenever possible. This improves type safety and makes the code more maintainable:
|
||||
|
||||
```rust
|
||||
// INCORRECT: Returning Dynamic for everything
|
||||
pub fn list_repositories(tree: &mut GitTree) -> Dynamic {
|
||||
let tree_clone = tree.clone();
|
||||
tree_clone.wrap(|t| {
|
||||
match t.list() {
|
||||
Ok(repos) => repos,
|
||||
Err(err) => vec![format!("Error listing repositories: {}", err)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// CORRECT: Using proper Result types
|
||||
pub fn list_repositories(tree: &mut GitTree) -> Result<Vec<String>, Box<EvalAltResult>> {
|
||||
let tree_clone = tree.clone();
|
||||
tree_clone.list().map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
format!("Error listing repositories: {}", err).into(),
|
||||
rhai::Position::NONE
|
||||
))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Improper Error Handling
|
||||
|
||||
```
|
||||
error[E0277]: the trait bound `for<'a> fn(&'a mut Engine) -> Result<(), Box<EvalAltResult>> {wrapper::register_git_module}: RhaiNativeFunc<_, _, _, _, _>` is not satisfied
|
||||
```
|
||||
|
||||
**Solution**: When registering functions that return Result types, make sure they are properly handled:
|
||||
|
||||
```rust
|
||||
// INCORRECT: Trying to register a function that returns Result<(), Box<EvalAltResult>>
|
||||
engine.register_fn("register_git_module", wrapper::register_git_module);
|
||||
|
||||
// CORRECT: Wrap the function to handle the Result
|
||||
engine.register_fn("register_git_module", |engine: &mut Engine| {
|
||||
match wrapper::register_git_module(engine) {
|
||||
Ok(_) => Dynamic::from(true),
|
||||
Err(err) => Dynamic::from(format!("Error: {}", err))
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Remember to adapt these solutions to your specific code context. The key is to maintain type consistency, proper visibility, correct lifetime management, and appropriate static typing.
|
||||
@@ -1,40 +0,0 @@
|
||||
## Example Rhai Script
|
||||
|
||||
Now, given the source code you wrapped using Rhai executable functions, write an example Rhai script that uses those functions.
|
||||
|
||||
### Example example rhai script
|
||||
|
||||
```rhai
|
||||
// example.rhai
|
||||
// Create a new GitTree instance
|
||||
let git_tree = new_git_tree("/Users/timurgordon/code");
|
||||
print("\nCreated GitTree for: /Users/timurgordon/code");
|
||||
|
||||
// List repositories in the tree
|
||||
let repos = list_repositories(git_tree);
|
||||
print("Found " + repos.len() + " repositories");
|
||||
|
||||
if repos.len() > 0 {
|
||||
print("First repository: " + repos[0]);
|
||||
|
||||
// Get the repository
|
||||
let repo_array = get_repositories(git_tree, repos[0]);
|
||||
|
||||
if repo_array.len() > 0 {
|
||||
let repo = repo_array[0];
|
||||
print("\nRepository path: " + path(repo));
|
||||
|
||||
// Check if the repository has changes
|
||||
let has_changes = has_changes(repo);
|
||||
print("Has changes: " + has_changes);
|
||||
|
||||
// Try to pull the repository
|
||||
print("\nTrying to pull repository...");
|
||||
let pull_result = pull_repository(repo);
|
||||
print("Pull result: " + pull_result);
|
||||
}
|
||||
}
|
||||
|
||||
print("\nResult: Git operations completed successfully");
|
||||
42 // Return value
|
||||
```
|
||||
@@ -1,99 +0,0 @@
|
||||
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"
|
||||
- @{source_pkg_info.name} = { path = "@{source_pkg_info.path}" }
|
||||
|
||||
3. For the wrapper: `use @{source_pkg_info.name}::@{source_pkg_info.module};` this way you can access the module functions and objects with @{source_pkg_info.module}::
|
||||
|
||||
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 @{source_pkg_info.name}::@{source_pkg_info.module}`
|
||||
your engine create function is called `create_rhai_engine`
|
||||
@@ -1,473 +0,0 @@
|
||||
|
||||
# Wrapper
|
||||
|
||||
Here is an example of a well-implemented Rhai wrapper for the Git module:
|
||||
|
||||
## Example wrapper
|
||||
|
||||
```rust
|
||||
// wrapper.rs
|
||||
//! Rhai wrappers for Nerdctl module functions
|
||||
//!
|
||||
//! This module provides Rhai wrappers for the functions in the Nerdctl module.
|
||||
|
||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
||||
use crate::virt::nerdctl::{self, NerdctlError, Image, Container};
|
||||
use crate::process::CommandResult;
|
||||
|
||||
// Helper functions for error conversion with improved context
|
||||
fn nerdctl_error_to_rhai_error<T>(result: Result<T, NerdctlError>) -> Result<T, Box<EvalAltResult>> {
|
||||
result.map_err(|e| {
|
||||
// Create a more detailed error message based on the error type
|
||||
let error_message = match &e {
|
||||
NerdctlError::CommandExecutionFailed(io_err) => {
|
||||
format!("Failed to execute nerdctl command: {}. This may indicate nerdctl is not installed or not in PATH.", io_err)
|
||||
},
|
||||
NerdctlError::CommandFailed(msg) => {
|
||||
format!("Nerdctl command failed: {}. Check container status and logs for more details.", msg)
|
||||
},
|
||||
NerdctlError::JsonParseError(msg) => {
|
||||
format!("Failed to parse nerdctl JSON output: {}. This may indicate an incompatible nerdctl version.", msg)
|
||||
},
|
||||
NerdctlError::ConversionError(msg) => {
|
||||
format!("Data conversion error: {}. This may indicate unexpected output format from nerdctl.", msg)
|
||||
},
|
||||
NerdctlError::Other(msg) => {
|
||||
format!("Nerdctl error: {}. This is an unexpected error.", msg)
|
||||
},
|
||||
};
|
||||
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
error_message.into(),
|
||||
rhai::Position::NONE
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Container Builder Pattern Implementation
|
||||
//
|
||||
|
||||
/// Create a new Container
|
||||
pub fn container_new(name: &str) -> Result<Container, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(Container::new(name))
|
||||
}
|
||||
|
||||
/// Create a Container from an image
|
||||
pub fn container_from_image(name: &str, image: &str) -> Result<Container, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(Container::from_image(name, image))
|
||||
}
|
||||
|
||||
/// Reset the container configuration to defaults while keeping the name and image
|
||||
pub fn container_reset(container: Container) -> Container {
|
||||
container.reset()
|
||||
}
|
||||
|
||||
/// Add a port mapping to a Container
|
||||
pub fn container_with_port(container: Container, port: &str) -> Container {
|
||||
container.with_port(port)
|
||||
}
|
||||
|
||||
/// Add a volume mount to a Container
|
||||
pub fn container_with_volume(container: Container, volume: &str) -> Container {
|
||||
container.with_volume(volume)
|
||||
}
|
||||
|
||||
/// Add an environment variable to a Container
|
||||
pub fn container_with_env(container: Container, key: &str, value: &str) -> Container {
|
||||
container.with_env(key, value)
|
||||
}
|
||||
|
||||
/// Set the network for a Container
|
||||
pub fn container_with_network(container: Container, network: &str) -> Container {
|
||||
container.with_network(network)
|
||||
}
|
||||
|
||||
/// Add a network alias to a Container
|
||||
pub fn container_with_network_alias(container: Container, alias: &str) -> Container {
|
||||
container.with_network_alias(alias)
|
||||
}
|
||||
|
||||
/// Set CPU limit for a Container
|
||||
pub fn container_with_cpu_limit(container: Container, cpus: &str) -> Container {
|
||||
container.with_cpu_limit(cpus)
|
||||
}
|
||||
|
||||
/// Set memory limit for a Container
|
||||
pub fn container_with_memory_limit(container: Container, memory: &str) -> Container {
|
||||
container.with_memory_limit(memory)
|
||||
}
|
||||
|
||||
/// Set restart policy for a Container
|
||||
pub fn container_with_restart_policy(container: Container, policy: &str) -> Container {
|
||||
container.with_restart_policy(policy)
|
||||
}
|
||||
|
||||
/// Set health check for a Container
|
||||
pub fn container_with_health_check(container: Container, cmd: &str) -> Container {
|
||||
container.with_health_check(cmd)
|
||||
}
|
||||
|
||||
/// Add multiple port mappings to a Container
|
||||
pub fn container_with_ports(mut container: Container, ports: Array) -> Container {
|
||||
for port in ports.iter() {
|
||||
if port.is_string() {
|
||||
let port_str = port.clone().cast::<String>();
|
||||
container = container.with_port(&port_str);
|
||||
}
|
||||
}
|
||||
container
|
||||
}
|
||||
|
||||
/// Add multiple volume mounts to a Container
|
||||
pub fn container_with_volumes(mut container: Container, volumes: Array) -> Container {
|
||||
for volume in volumes.iter() {
|
||||
if volume.is_string() {
|
||||
let volume_str = volume.clone().cast::<String>();
|
||||
container = container.with_volume(&volume_str);
|
||||
}
|
||||
}
|
||||
container
|
||||
}
|
||||
|
||||
/// Add multiple environment variables to a Container
|
||||
pub fn container_with_envs(mut container: Container, env_map: Map) -> Container {
|
||||
for (key, value) in env_map.iter() {
|
||||
if value.is_string() {
|
||||
let value_str = value.clone().cast::<String>();
|
||||
container = container.with_env(&key, &value_str);
|
||||
}
|
||||
}
|
||||
container
|
||||
}
|
||||
|
||||
/// Add multiple network aliases to a Container
|
||||
pub fn container_with_network_aliases(mut container: Container, aliases: Array) -> Container {
|
||||
for alias in aliases.iter() {
|
||||
if alias.is_string() {
|
||||
let alias_str = alias.clone().cast::<String>();
|
||||
container = container.with_network_alias(&alias_str);
|
||||
}
|
||||
}
|
||||
container
|
||||
}
|
||||
|
||||
/// Set memory swap limit for a Container
|
||||
pub fn container_with_memory_swap_limit(container: Container, memory_swap: &str) -> Container {
|
||||
container.with_memory_swap_limit(memory_swap)
|
||||
}
|
||||
|
||||
/// Set CPU shares for a Container
|
||||
pub fn container_with_cpu_shares(container: Container, shares: &str) -> Container {
|
||||
container.with_cpu_shares(shares)
|
||||
}
|
||||
|
||||
/// Set health check with options for a Container
|
||||
pub fn container_with_health_check_options(
|
||||
container: Container,
|
||||
cmd: &str,
|
||||
interval: Option<&str>,
|
||||
timeout: Option<&str>,
|
||||
retries: Option<i64>,
|
||||
start_period: Option<&str>
|
||||
) -> Container {
|
||||
// Convert i64 to u32 for retries
|
||||
let retries_u32 = retries.map(|r| r as u32);
|
||||
container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period)
|
||||
}
|
||||
|
||||
/// Set snapshotter for a Container
|
||||
pub fn container_with_snapshotter(container: Container, snapshotter: &str) -> Container {
|
||||
container.with_snapshotter(snapshotter)
|
||||
}
|
||||
|
||||
/// Set detach mode for a Container
|
||||
pub fn container_with_detach(container: Container, detach: bool) -> Container {
|
||||
container.with_detach(detach)
|
||||
}
|
||||
|
||||
/// Build and run the Container
|
||||
///
|
||||
/// This function builds and runs the container using the configured options.
|
||||
/// It provides detailed error information if the build fails.
|
||||
pub fn container_build(container: Container) -> Result<Container, Box<EvalAltResult>> {
|
||||
// Get container details for better error reporting
|
||||
let container_name = container.name.clone();
|
||||
let image = container.image.clone().unwrap_or_else(|| "none".to_string());
|
||||
let ports = container.ports.clone();
|
||||
let volumes = container.volumes.clone();
|
||||
let env_vars = container.env_vars.clone();
|
||||
|
||||
// Try to build the container
|
||||
let build_result = container.build();
|
||||
|
||||
// Handle the result with improved error context
|
||||
match build_result {
|
||||
Ok(built_container) => {
|
||||
// Container built successfully
|
||||
Ok(built_container)
|
||||
},
|
||||
Err(err) => {
|
||||
// Add more context to the error
|
||||
let enhanced_error = match err {
|
||||
NerdctlError::CommandFailed(msg) => {
|
||||
// Provide more detailed error information
|
||||
let mut enhanced_msg = format!("Failed to build container '{}' from image '{}': {}",
|
||||
container_name, image, msg);
|
||||
|
||||
// Add information about configured options that might be relevant
|
||||
if !ports.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports));
|
||||
}
|
||||
|
||||
if !volumes.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes));
|
||||
}
|
||||
|
||||
if !env_vars.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured environment variables: {:?}", env_vars));
|
||||
}
|
||||
|
||||
// Add suggestions for common issues
|
||||
if msg.contains("not found") || msg.contains("no such image") {
|
||||
enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull().");
|
||||
} else if msg.contains("port is already allocated") {
|
||||
enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port.");
|
||||
} else if msg.contains("permission denied") {
|
||||
enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes.");
|
||||
}
|
||||
|
||||
NerdctlError::CommandFailed(enhanced_msg)
|
||||
},
|
||||
_ => err
|
||||
};
|
||||
|
||||
nerdctl_error_to_rhai_error(Err(enhanced_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the Container and verify it's running
|
||||
///
|
||||
/// This function starts the container and verifies that it's actually running.
|
||||
/// It returns detailed error information if the container fails to start or
|
||||
/// if it starts but stops immediately.
|
||||
pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
// Get container details for better error reporting
|
||||
let container_name = container.name.clone();
|
||||
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
// Try to start the container
|
||||
let start_result = container.start();
|
||||
|
||||
// Handle the result with improved error context
|
||||
match start_result {
|
||||
Ok(result) => {
|
||||
// Container started successfully
|
||||
Ok(result)
|
||||
},
|
||||
Err(err) => {
|
||||
// Add more context to the error
|
||||
let enhanced_error = match err {
|
||||
NerdctlError::CommandFailed(msg) => {
|
||||
// Check if this is a "container already running" error, which is not really an error
|
||||
if msg.contains("already running") {
|
||||
return Ok(CommandResult {
|
||||
stdout: format!("Container {} is already running", container_name),
|
||||
stderr: "".to_string(),
|
||||
success: true,
|
||||
code: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Try to get more information about why the container might have failed to start
|
||||
let mut enhanced_msg = format!("Failed to start container '{}' (ID: {}): {}",
|
||||
container_name, container_id, msg);
|
||||
|
||||
// Try to check if the image exists
|
||||
if let Some(image) = &container.image {
|
||||
enhanced_msg.push_str(&format!("\nContainer was using image: {}", image));
|
||||
}
|
||||
|
||||
NerdctlError::CommandFailed(enhanced_msg)
|
||||
},
|
||||
_ => err
|
||||
};
|
||||
|
||||
nerdctl_error_to_rhai_error(Err(enhanced_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the Container
|
||||
pub fn container_stop(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(container.stop())
|
||||
}
|
||||
|
||||
/// Remove the Container
|
||||
pub fn container_remove(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(container.remove())
|
||||
}
|
||||
|
||||
/// Execute a command in the Container
|
||||
pub fn container_exec(container: &mut Container, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(container.exec(command))
|
||||
}
|
||||
|
||||
/// Get container logs
|
||||
pub fn container_logs(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
// Get container details for better error reporting
|
||||
let container_name = container.name.clone();
|
||||
let container_id = container.container_id.clone().unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
// Use the nerdctl::logs function
|
||||
let logs_result = nerdctl::logs(&container_id);
|
||||
|
||||
match logs_result {
|
||||
Ok(result) => {
|
||||
Ok(result)
|
||||
},
|
||||
Err(err) => {
|
||||
// Add more context to the error
|
||||
let enhanced_error = NerdctlError::CommandFailed(
|
||||
format!("Failed to get logs for container '{}' (ID: {}): {}",
|
||||
container_name, container_id, err)
|
||||
);
|
||||
|
||||
nerdctl_error_to_rhai_error(Err(enhanced_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy files between the Container and local filesystem
|
||||
pub fn container_copy(container: &mut Container, source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(container.copy(source, dest))
|
||||
}
|
||||
|
||||
/// Create a new Map with default run options
|
||||
pub fn new_run_options() -> Map {
|
||||
let mut map = Map::new();
|
||||
map.insert("name".into(), Dynamic::UNIT);
|
||||
map.insert("detach".into(), Dynamic::from(true));
|
||||
map.insert("ports".into(), Dynamic::from(Array::new()));
|
||||
map.insert("snapshotter".into(), Dynamic::from("native"));
|
||||
map
|
||||
}
|
||||
|
||||
//
|
||||
// Container Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for nerdctl::run
|
||||
///
|
||||
/// Run a container from an image.
|
||||
pub fn nerdctl_run(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::run(image, None, true, None, None))
|
||||
}
|
||||
|
||||
/// Run a container with a name
|
||||
pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, None, None))
|
||||
}
|
||||
|
||||
/// Run a container with a port mapping
|
||||
pub fn nerdctl_run_with_port(image: &str, name: &str, port: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
let ports = vec![port];
|
||||
nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, Some(&ports), None))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::exec
|
||||
///
|
||||
/// Execute a command in a container.
|
||||
pub fn nerdctl_exec(container: &str, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::exec(container, command))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::copy
|
||||
///
|
||||
/// Copy files between container and local filesystem.
|
||||
pub fn nerdctl_copy(source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::copy(source, dest))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::stop
|
||||
///
|
||||
/// Stop a container.
|
||||
pub fn nerdctl_stop(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::stop(container))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::remove
|
||||
///
|
||||
/// Remove a container.
|
||||
pub fn nerdctl_remove(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::remove(container))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::list
|
||||
///
|
||||
/// List containers.
|
||||
pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::list(all))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::logs
|
||||
///
|
||||
/// Get container logs.
|
||||
pub fn nerdctl_logs(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::logs(container))
|
||||
}
|
||||
|
||||
//
|
||||
// Image Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for nerdctl::images
|
||||
///
|
||||
/// List images in local storage.
|
||||
pub fn nerdctl_images() -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::images())
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_remove
|
||||
///
|
||||
/// Remove one or more images.
|
||||
pub fn nerdctl_image_remove(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_remove(image))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_push
|
||||
///
|
||||
/// Push an image to a registry.
|
||||
pub fn nerdctl_image_push(image: &str, destination: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_tag
|
||||
///
|
||||
/// Add an additional name to a local image.
|
||||
pub fn nerdctl_image_tag(image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_tag(image, new_name))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_pull
|
||||
///
|
||||
/// Pull an image from a registry.
|
||||
pub fn nerdctl_image_pull(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_pull(image))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_commit
|
||||
///
|
||||
/// Commit a container to an image.
|
||||
pub fn nerdctl_image_commit(container: &str, image_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_commit(container, image_name))
|
||||
}
|
||||
|
||||
/// Wrapper for nerdctl::image_build
|
||||
///
|
||||
/// Build an image using a Dockerfile.
|
||||
pub fn nerdctl_image_build(tag: &str, context_path: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path))
|
||||
}
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "@{name}_rhai"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.21.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
@{source_pkg_info.name} = { path = "@{source_pkg_info.path}" }
|
||||
@@ -1,12 +0,0 @@
|
||||
use rhai::{Engine, EvalAltResult, Map, Dynamic};
|
||||
use crate::wrapper;
|
||||
|
||||
pub fn create_rhai_engine() -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
@for function in functions
|
||||
engine.register_fn("@{function}", wrapper::@{function});
|
||||
@end
|
||||
|
||||
engine
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
use std::{fs, path::Path};
|
||||
use @{name}_rhai::create_rhai_engine;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Rhai Wrapper Example ===");
|
||||
|
||||
// Create a Rhai engine with functionality
|
||||
let mut engine = create_rhai_engine();
|
||||
println!("Successfully created Rhai engine");
|
||||
|
||||
// Get the path to the example.rhai script
|
||||
let script_path = get_script_path()?;
|
||||
println!("Loading script from: {}", script_path.display());
|
||||
|
||||
// Load the script content
|
||||
let script = fs::read_to_string(&script_path)
|
||||
.map_err(|e| format!("Failed to read script file: {}", e))?;
|
||||
|
||||
// Run the script
|
||||
println!("\n=== Running Rhai script ===");
|
||||
let result = engine.eval::<i64>(&script)
|
||||
.map_err(|e| format!("Script execution error: {}", e))?;
|
||||
|
||||
println!("\nScript returned: {}", result);
|
||||
println!("\nExample completed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_script_path() -> Result<std::path::PathBuf, String> {
|
||||
// When running with cargo run --example, the script will be in the examples directory
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("example.rhai");
|
||||
|
||||
if script_path.exists() {
|
||||
Ok(script_path)
|
||||
} else {
|
||||
Err(format!("Could not find example.rhai script at {}", script_path.display()))
|
||||
}
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
// File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container_builder.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};
|
||||
use super::container_types::{Container, HealthCheck};
|
||||
use super::health_check_script::prepare_health_check_command;
|
||||
|
||||
impl Container {
|
||||
/// Reset the container configuration to defaults while keeping the name and image
|
||||
/// If the container exists, it will be stopped and removed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn reset(self) -> Self {
|
||||
let name = self.name;
|
||||
let image = self.image.clone();
|
||||
|
||||
// If container exists, stop and remove it
|
||||
if let Some(container_id) = &self.container_id {
|
||||
println!("Container exists. Stopping and removing container '{}'...", name);
|
||||
|
||||
// Try to stop the container
|
||||
let _ = execute_nerdctl_command(&["stop", container_id]);
|
||||
|
||||
// Try to remove the container
|
||||
let _ = execute_nerdctl_command(&["rm", container_id]);
|
||||
}
|
||||
|
||||
// Create a new container with just the name and image, but no container_id
|
||||
Self {
|
||||
name,
|
||||
container_id: None, // Reset container_id to None since we removed the container
|
||||
image,
|
||||
config: std::collections::HashMap::new(),
|
||||
ports: Vec::new(),
|
||||
volumes: Vec::new(),
|
||||
env_vars: std::collections::HashMap::new(),
|
||||
network: None,
|
||||
network_aliases: Vec::new(),
|
||||
cpu_limit: None,
|
||||
memory_limit: None,
|
||||
memory_swap_limit: None,
|
||||
cpu_shares: None,
|
||||
restart_policy: None,
|
||||
health_check: None,
|
||||
detach: false,
|
||||
snapshotter: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a port mapping
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `port` - Port mapping (e.g., "8080:80")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_port(mut self, port: &str) -> Self {
|
||||
self.ports.push(port.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple port mappings
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ports` - Array of port mappings (e.g., ["8080:80", "8443:443"])
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_ports(mut self, ports: &[&str]) -> Self {
|
||||
for port in ports {
|
||||
self.ports.push(port.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a volume mount
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `volume` - Volume mount (e.g., "/host/path:/container/path")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_volume(mut self, volume: &str) -> Self {
|
||||
self.volumes.push(volume.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple volume mounts
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `volumes` - Array of volume mounts (e.g., ["/host/path1:/container/path1", "/host/path2:/container/path2"])
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_volumes(mut self, volumes: &[&str]) -> Self {
|
||||
for volume in volumes {
|
||||
self.volumes.push(volume.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an environment variable
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - Environment variable name
|
||||
/// * `value` - Environment variable value
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_env(mut self, key: &str, value: &str) -> Self {
|
||||
self.env_vars.insert(key.to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple environment variables
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `env_map` - Map of environment variable names to values
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_envs(mut self, env_map: &HashMap<&str, &str>) -> Self {
|
||||
for (key, value) in env_map {
|
||||
self.env_vars.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the network for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `network` - Network name
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_network(mut self, network: &str) -> Self {
|
||||
self.network = Some(network.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a network alias for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `alias` - Network alias
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_network_alias(mut self, alias: &str) -> Self {
|
||||
self.network_aliases.push(alias.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple network aliases for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `aliases` - Array of network aliases
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_network_aliases(mut self, aliases: &[&str]) -> Self {
|
||||
for alias in aliases {
|
||||
self.network_aliases.push(alias.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set CPU limit for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cpus` - CPU limit (e.g., "0.5" for half a CPU, "2" for 2 CPUs)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_cpu_limit(mut self, cpus: &str) -> Self {
|
||||
self.cpu_limit = Some(cpus.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set memory limit for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `memory` - Memory limit (e.g., "512m" for 512MB, "1g" for 1GB)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_memory_limit(mut self, memory: &str) -> Self {
|
||||
self.memory_limit = Some(memory.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set memory swap limit for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `memory_swap` - Memory swap limit (e.g., "1g" for 1GB)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_memory_swap_limit(mut self, memory_swap: &str) -> Self {
|
||||
self.memory_swap_limit = Some(memory_swap.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set CPU shares for the container (relative weight)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `shares` - CPU shares (e.g., "1024" for default, "512" for half)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_cpu_shares(mut self, shares: &str) -> Self {
|
||||
self.cpu_shares = Some(shares.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set restart policy for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `policy` - Restart policy (e.g., "no", "always", "on-failure", "unless-stopped")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_restart_policy(mut self, policy: &str) -> Self {
|
||||
self.restart_policy = Some(policy.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a simple health check for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cmd` - Command to run for health check (e.g., "curl -f http://localhost/ || exit 1")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_health_check(mut self, cmd: &str) -> Self {
|
||||
// Use the health check script module to prepare the command
|
||||
let prepared_cmd = prepare_health_check_command(cmd, &self.name);
|
||||
|
||||
self.health_check = Some(HealthCheck {
|
||||
cmd: prepared_cmd,
|
||||
interval: None,
|
||||
timeout: None,
|
||||
retries: None,
|
||||
start_period: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a health check with custom options for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cmd` - Command to run for health check
|
||||
/// * `interval` - Optional time between running the check (e.g., "30s", "1m")
|
||||
/// * `timeout` - Optional maximum time to wait for a check to complete (e.g., "30s", "1m")
|
||||
/// * `retries` - Optional number of consecutive failures needed to consider unhealthy
|
||||
/// * `start_period` - Optional start period for the container to initialize before counting retries (e.g., "30s", "1m")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_health_check_options(
|
||||
mut self,
|
||||
cmd: &str,
|
||||
interval: Option<&str>,
|
||||
timeout: Option<&str>,
|
||||
retries: Option<u32>,
|
||||
start_period: Option<&str>,
|
||||
) -> Self {
|
||||
// Use the health check script module to prepare the command
|
||||
let prepared_cmd = prepare_health_check_command(cmd, &self.name);
|
||||
|
||||
let mut health_check = HealthCheck {
|
||||
cmd: prepared_cmd,
|
||||
interval: None,
|
||||
timeout: None,
|
||||
retries: None,
|
||||
start_period: None,
|
||||
};
|
||||
|
||||
if let Some(interval_value) = interval {
|
||||
health_check.interval = Some(interval_value.to_string());
|
||||
}
|
||||
|
||||
if let Some(timeout_value) = timeout {
|
||||
health_check.timeout = Some(timeout_value.to_string());
|
||||
}
|
||||
|
||||
if let Some(retries_value) = retries {
|
||||
health_check.retries = Some(retries_value);
|
||||
}
|
||||
|
||||
if let Some(start_period_value) = start_period {
|
||||
health_check.start_period = Some(start_period_value.to_string());
|
||||
}
|
||||
|
||||
self.health_check = Some(health_check);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the snapshotter
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `snapshotter` - Snapshotter to use
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_snapshotter(mut self, snapshotter: &str) -> Self {
|
||||
self.snapshotter = Some(snapshotter.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to run in detached mode
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `detach` - Whether to run in detached mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_detach(mut self, detach: bool) -> Self {
|
||||
self.detach = detach;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the container
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Self, NerdctlError>` - Container instance or error
|
||||
pub fn build(self) -> Result<Self, NerdctlError> {
|
||||
// If container already exists, return it
|
||||
if self.container_id.is_some() {
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// If no image is specified, return an error
|
||||
let image = match &self.image {
|
||||
Some(img) => img,
|
||||
None => return Err(NerdctlError::Other("No image specified for container creation".to_string())),
|
||||
};
|
||||
|
||||
// Build the command arguments as strings
|
||||
let mut args_strings = Vec::new();
|
||||
args_strings.push("run".to_string());
|
||||
|
||||
if self.detach {
|
||||
args_strings.push("-d".to_string());
|
||||
}
|
||||
|
||||
args_strings.push("--name".to_string());
|
||||
args_strings.push(self.name.clone());
|
||||
|
||||
// Add port mappings
|
||||
for port in &self.ports {
|
||||
args_strings.push("-p".to_string());
|
||||
args_strings.push(port.clone());
|
||||
}
|
||||
|
||||
// Add volume mounts
|
||||
for volume in &self.volumes {
|
||||
args_strings.push("-v".to_string());
|
||||
args_strings.push(volume.clone());
|
||||
}
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in &self.env_vars {
|
||||
args_strings.push("-e".to_string());
|
||||
args_strings.push(format!("{}={}", key, value));
|
||||
}
|
||||
|
||||
// Add network configuration
|
||||
if let Some(network) = &self.network {
|
||||
args_strings.push("--network".to_string());
|
||||
args_strings.push(network.clone());
|
||||
}
|
||||
|
||||
// Add network aliases
|
||||
for alias in &self.network_aliases {
|
||||
args_strings.push("--network-alias".to_string());
|
||||
args_strings.push(alias.clone());
|
||||
}
|
||||
|
||||
// Add resource limits
|
||||
if let Some(cpu_limit) = &self.cpu_limit {
|
||||
args_strings.push("--cpus".to_string());
|
||||
args_strings.push(cpu_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(memory_limit) = &self.memory_limit {
|
||||
args_strings.push("--memory".to_string());
|
||||
args_strings.push(memory_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(memory_swap_limit) = &self.memory_swap_limit {
|
||||
args_strings.push("--memory-swap".to_string());
|
||||
args_strings.push(memory_swap_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(cpu_shares) = &self.cpu_shares {
|
||||
args_strings.push("--cpu-shares".to_string());
|
||||
args_strings.push(cpu_shares.clone());
|
||||
}
|
||||
|
||||
// Add restart policy
|
||||
if let Some(restart_policy) = &self.restart_policy {
|
||||
args_strings.push("--restart".to_string());
|
||||
args_strings.push(restart_policy.clone());
|
||||
}
|
||||
|
||||
// Add health check
|
||||
if let Some(health_check) = &self.health_check {
|
||||
args_strings.push("--health-cmd".to_string());
|
||||
args_strings.push(health_check.cmd.clone());
|
||||
|
||||
if let Some(interval) = &health_check.interval {
|
||||
args_strings.push("--health-interval".to_string());
|
||||
args_strings.push(interval.clone());
|
||||
}
|
||||
|
||||
if let Some(timeout) = &health_check.timeout {
|
||||
args_strings.push("--health-timeout".to_string());
|
||||
args_strings.push(timeout.clone());
|
||||
}
|
||||
|
||||
if let Some(retries) = &health_check.retries {
|
||||
args_strings.push("--health-retries".to_string());
|
||||
args_strings.push(retries.to_string());
|
||||
}
|
||||
|
||||
if let Some(start_period) = &health_check.start_period {
|
||||
args_strings.push("--health-start-period".to_string());
|
||||
args_strings.push(start_period.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(snapshotter_value) = &self.snapshotter {
|
||||
args_strings.push("--snapshotter".to_string());
|
||||
args_strings.push(snapshotter_value.clone());
|
||||
}
|
||||
|
||||
// Add flags to avoid BPF issues
|
||||
args_strings.push("--cgroup-manager=cgroupfs".to_string());
|
||||
|
||||
args_strings.push(image.clone());
|
||||
|
||||
// Convert to string slices for the command
|
||||
let args: Vec<&str> = args_strings.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
// Execute the command
|
||||
let result = execute_nerdctl_command(&args)?;
|
||||
|
||||
// Get the container ID from the output
|
||||
let container_id = result.stdout.trim().to_string();
|
||||
|
||||
Ok(Self {
|
||||
name: self.name,
|
||||
container_id: Some(container_id),
|
||||
image: self.image,
|
||||
config: self.config,
|
||||
ports: self.ports,
|
||||
volumes: self.volumes,
|
||||
env_vars: self.env_vars,
|
||||
network: self.network,
|
||||
network_aliases: self.network_aliases,
|
||||
cpu_limit: self.cpu_limit,
|
||||
memory_limit: self.memory_limit,
|
||||
memory_swap_limit: self.memory_swap_limit,
|
||||
cpu_shares: self.cpu_shares,
|
||||
restart_policy: self.restart_policy,
|
||||
health_check: self.health_check,
|
||||
detach: self.detach,
|
||||
snapshotter: self.snapshotter,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use rhai::{Dynamic, Map, Array};
|
||||
|
||||
/// Local wrapper trait for sal::rhai::ToRhai to avoid orphan rule violations
|
||||
pub trait ToRhai {
|
||||
/// Convert to a Rhai Dynamic value
|
||||
fn to_rhai(&self) -> Dynamic;
|
||||
}
|
||||
|
||||
// Implementation of ToRhai for Dynamic
|
||||
impl ToRhai for Dynamic {
|
||||
fn to_rhai(&self) -> Dynamic {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic trait for wrapping Rust functions to be used with Rhai
|
||||
pub trait RhaiWrapper {
|
||||
/// Wrap a function that takes ownership of self
|
||||
fn wrap_consuming<F, R>(self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(Self) -> R,
|
||||
R: ToRhai;
|
||||
|
||||
/// Wrap a function that takes a mutable reference to self
|
||||
fn wrap_mut<F, R>(&mut self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
R: ToRhai;
|
||||
|
||||
/// Wrap a function that takes an immutable reference to self
|
||||
fn wrap<F, R>(&self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(&Self) -> R,
|
||||
R: ToRhai;
|
||||
}
|
||||
|
||||
/// Implementation of RhaiWrapper for any type
|
||||
impl<T> RhaiWrapper for T {
|
||||
fn wrap_consuming<F, R>(self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(Self) -> R,
|
||||
R: ToRhai,
|
||||
{
|
||||
let result = f(self);
|
||||
result.to_rhai()
|
||||
}
|
||||
|
||||
fn wrap_mut<F, R>(&mut self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
R: ToRhai,
|
||||
{
|
||||
let result = f(self);
|
||||
result.to_rhai()
|
||||
}
|
||||
|
||||
fn wrap<F, R>(&self, f: F) -> Dynamic
|
||||
where
|
||||
Self: Sized + Clone,
|
||||
F: FnOnce(&Self) -> R,
|
||||
R: ToRhai,
|
||||
{
|
||||
let result = f(self);
|
||||
result.to_rhai()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a Rhai Map to a Rust HashMap
|
||||
pub fn map_to_hashmap(map: &Map) -> HashMap<String, String> {
|
||||
let mut result = HashMap::new();
|
||||
for (key, value) in map.iter() {
|
||||
let k = key.clone().to_string();
|
||||
let v = value.clone().to_string();
|
||||
if !k.is_empty() && !v.is_empty() {
|
||||
result.insert(k, v);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert a HashMap<String, String> to a Rhai Map
|
||||
pub fn hashmap_to_map(map: &HashMap<String, String>) -> Map {
|
||||
let mut result = Map::new();
|
||||
for (key, value) in map.iter() {
|
||||
result.insert(key.clone().into(), Dynamic::from(value.clone()));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert a Rhai Array to a Vec of strings
|
||||
pub fn array_to_vec_string(array: &Array) -> Vec<String> {
|
||||
array.iter()
|
||||
.filter_map(|item| {
|
||||
let s = item.clone().to_string();
|
||||
if !s.is_empty() { Some(s) } else { None }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Helper function to convert Dynamic to Option<String>
|
||||
pub fn dynamic_to_string_option(value: &Dynamic) -> Option<String> {
|
||||
if value.is_string() {
|
||||
Some(value.clone().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert Dynamic to Option<u32>
|
||||
pub fn dynamic_to_u32_option(value: &Dynamic) -> Option<u32> {
|
||||
if value.is_int() {
|
||||
Some(value.as_int().unwrap() as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert Dynamic to Option<&str> with lifetime management
|
||||
pub fn dynamic_to_str_option<'a>(value: &Dynamic, storage: &'a mut String) -> Option<&'a str> {
|
||||
if value.is_string() {
|
||||
*storage = value.clone().to_string();
|
||||
Some(storage.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Re-export the utility modules
|
||||
pub mod generic_wrapper;
|
||||
pub mod wrapper;
|
||||
pub mod engine;
|
||||
|
||||
// Re-export the utility traits and functions
|
||||
pub use generic_wrapper::{RhaiWrapper, map_to_hashmap, array_to_vec_string,
|
||||
dynamic_to_string_option, hashmap_to_map};
|
||||
pub use engine::create_rhai_engine;
|
||||
|
||||
// The create_rhai_engine function is now in the engine module
|
||||
@@ -1,22 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import cli
|
||||
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rhai'
|
||||
// execute: cmd_mcpgen
|
||||
description: 'rhai command'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Rhai server'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
mut server := new_mcp_server()!
|
||||
server.start()!
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import log
|
||||
|
||||
pub fn new_mcp_server() !&mcp.Server {
|
||||
log.info('Creating new Developer MCP server')
|
||||
|
||||
// Initialize the server with the empty handlers map
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'generate_rhai_wrapper': generate_rhai_wrapper_spec
|
||||
}
|
||||
tool_handlers: {
|
||||
'generate_rhai_wrapper': generate_rhai_wrapper_handler
|
||||
}
|
||||
prompts: {
|
||||
'rhai_wrapper': rhai_wrapper_prompt_spec
|
||||
}
|
||||
prompt_handlers: {
|
||||
'rhai_wrapper': rhai_wrapper_prompt_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'rhai'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})!
|
||||
return server
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.ai.mcp.rhai.logic
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import x.json2 as json
|
||||
|
||||
// Tool definition for the create_rhai_wrapper function
|
||||
const rhai_wrapper_prompt_spec = mcp.Prompt{
|
||||
name: 'rhai_wrapper'
|
||||
description: 'provides a prompt for creating Rhai wrappers for Rust functions that follow builder pattern and create examples corresponding to the provided example file'
|
||||
arguments: [
|
||||
mcp.PromptArgument{
|
||||
name: 'source_path'
|
||||
description: 'Path to the source directory'
|
||||
required: true
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Tool handler for the create_rhai_wrapper function
|
||||
pub fn rhai_wrapper_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
source_path := arguments[0]
|
||||
|
||||
// Read and combine all Rust files in the source directory
|
||||
source_code := rust.read_source_code(source_path)!
|
||||
|
||||
// Extract the module name from the directory path (last component)
|
||||
name := rust.extract_module_name_from_path(source_path)
|
||||
|
||||
source_pkg_info := rust.detect_source_package(source_path)!
|
||||
|
||||
result := logic.rhai_wrapper_generation_prompt(name, source_code, source_pkg_info)!
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: result
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import log
|
||||
|
||||
const specs = mcp.Tool{
|
||||
name: 'rhai_interface'
|
||||
description: 'Add Rhai Interface to Rust Code Files'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to a .rs file or directory containing .rs files to make rhai interface for'
|
||||
})
|
||||
}
|
||||
required: ['path']
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.ai.mcp.rhai.logic
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2 as json { Any }
|
||||
|
||||
// Tool definition for the generate_rhai_wrapper function
|
||||
const generate_rhai_wrapper_spec = mcp.Tool{
|
||||
name: 'generate_rhai_wrapper'
|
||||
description: 'generate_rhai_wrapper receives the name of a V language function string, and the path to the module in which it exists.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'source_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['name', 'source_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Tool handler for the generate_rhai_wrapper function
|
||||
pub fn generate_rhai_wrapper_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
name := arguments['name'].str()
|
||||
source_path := arguments['source_path'].str()
|
||||
result := logic.generate_rhai_wrapper(name, source_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
module rhai
|
||||
@@ -1,21 +0,0 @@
|
||||
module rust
|
||||
|
||||
import cli
|
||||
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rust'
|
||||
description: 'Rust language tools command'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Rust MCP server'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
mut server := new_mcp_server()!
|
||||
server.start()!
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp { ToolContent }
|
||||
|
||||
pub fn result_to_mcp_tool_contents[T](result T) []ToolContent {
|
||||
return [result_to_mcp_tool_content[T](result)]
|
||||
}
|
||||
|
||||
pub fn result_to_mcp_tool_content[T](result T) ToolContent {
|
||||
$if T is string {
|
||||
return ToolContent{
|
||||
typ: 'text'
|
||||
text: result.str()
|
||||
}
|
||||
} $else $if T is int {
|
||||
return ToolContent{
|
||||
typ: 'number'
|
||||
number: result.int()
|
||||
}
|
||||
} $else $if T is bool {
|
||||
return ToolContent{
|
||||
typ: 'boolean'
|
||||
boolean: result.bool()
|
||||
}
|
||||
} $else $if result is $array {
|
||||
mut items := []ToolContent{}
|
||||
for item in result {
|
||||
items << result_to_mcp_tool_content(item)
|
||||
}
|
||||
return ToolContent{
|
||||
typ: 'array'
|
||||
items: items
|
||||
}
|
||||
} $else $if T is $struct {
|
||||
mut properties := map[string]ToolContent{}
|
||||
$for field in T.fields {
|
||||
properties[field.name] = result_to_mcp_tool_content(result.$(field.name))
|
||||
}
|
||||
return ToolContent{
|
||||
typ: 'object'
|
||||
properties: properties
|
||||
}
|
||||
} $else {
|
||||
panic('Unsupported type: ${typeof(result)}')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn array_to_mcp_tool_contents[U](array []U) []ToolContent {
|
||||
mut contents := []ToolContent{}
|
||||
for item in array {
|
||||
contents << result_to_mcp_tool_content(item)
|
||||
}
|
||||
return contents
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import log
|
||||
|
||||
pub fn new_mcp_server() !&mcp.Server {
|
||||
log.info('Creating new Rust MCP server')
|
||||
|
||||
// Initialize the server with tools and prompts
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'list_functions_in_file': list_functions_in_file_spec
|
||||
'list_structs_in_file': list_structs_in_file_spec
|
||||
'list_modules_in_dir': list_modules_in_dir_spec
|
||||
'get_import_statement': get_import_statement_spec
|
||||
// 'get_module_dependency': get_module_dependency_spec
|
||||
}
|
||||
tool_handlers: {
|
||||
'list_functions_in_file': list_functions_in_file_handler
|
||||
'list_structs_in_file': list_structs_in_file_handler
|
||||
'list_modules_in_dir': list_modules_in_dir_handler
|
||||
'get_import_statement': get_import_statement_handler
|
||||
// 'get_module_dependency': get_module_dependency_handler
|
||||
}
|
||||
prompts: {
|
||||
'rust_functions': rust_functions_prompt_spec
|
||||
'rust_structs': rust_structs_prompt_spec
|
||||
'rust_modules': rust_modules_prompt_spec
|
||||
'rust_imports': rust_imports_prompt_spec
|
||||
'rust_dependencies': rust_dependencies_prompt_spec
|
||||
'rust_tools_guide': rust_tools_guide_prompt_spec
|
||||
}
|
||||
prompt_handlers: {
|
||||
'rust_functions': rust_functions_prompt_handler
|
||||
'rust_structs': rust_structs_prompt_handler
|
||||
'rust_modules': rust_modules_prompt_handler
|
||||
'rust_imports': rust_imports_prompt_handler
|
||||
'rust_dependencies': rust_dependencies_prompt_handler
|
||||
'rust_tools_guide': rust_tools_guide_prompt_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'rust'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})!
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import os
|
||||
import x.json2 as json
|
||||
|
||||
// Prompt specification for Rust functions
|
||||
const rust_functions_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_functions'
|
||||
description: 'Provides guidance on working with Rust functions and using the list_functions_in_file tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_functions prompt
|
||||
pub fn rust_functions_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/functions.md')!
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust structs
|
||||
const rust_structs_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_structs'
|
||||
description: 'Provides guidance on working with Rust structs and using the list_structs_in_file tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_structs prompt
|
||||
pub fn rust_structs_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/structs.md')!
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust modules
|
||||
const rust_modules_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_modules'
|
||||
description: 'Provides guidance on working with Rust modules and using the list_modules_in_dir tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_modules prompt
|
||||
pub fn rust_modules_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/modules.md')!
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust imports
|
||||
const rust_imports_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_imports'
|
||||
description: 'Provides guidance on working with Rust imports and using the get_import_statement tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_imports prompt
|
||||
pub fn rust_imports_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/imports.md')!
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust dependencies
|
||||
const rust_dependencies_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_dependencies'
|
||||
description: 'Provides guidance on working with Rust dependencies and using the get_module_dependency tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_dependencies prompt
|
||||
pub fn rust_dependencies_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/dependencies.md')!
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Prompt specification for general Rust tools guide
|
||||
const rust_tools_guide_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_tools_guide'
|
||||
description: 'Provides a comprehensive guide on all available Rust tools and how to use them'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_tools_guide prompt
|
||||
pub fn rust_tools_guide_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
// Combine all prompt files into one comprehensive guide
|
||||
functions_content := os.read_file('${os.dir(@FILE)}/prompts/functions.md')!
|
||||
structs_content := os.read_file('${os.dir(@FILE)}/prompts/structs.md')!
|
||||
modules_content := os.read_file('${os.dir(@FILE)}/prompts/modules.md')!
|
||||
imports_content := os.read_file('${os.dir(@FILE)}/prompts/imports.md')!
|
||||
dependencies_content := os.read_file('${os.dir(@FILE)}/prompts/dependencies.md')!
|
||||
|
||||
combined_content := '# Rust Language Tools Guide\n\n' +
|
||||
'This guide provides comprehensive information on working with Rust code using the available tools.\n\n' +
|
||||
'## Table of Contents\n\n' + '1. [Functions](#functions)\n' + '2. [Structs](#structs)\n' +
|
||||
'3. [Modules](#modules)\n' + '4. [Imports](#imports)\n' +
|
||||
'5. [Dependencies](#dependencies)\n\n' + '<a name="functions"></a>\n' + functions_content +
|
||||
'\n\n' + '<a name="structs"></a>\n' + structs_content + '\n\n' +
|
||||
'<a name="modules"></a>\n' + modules_content + '\n\n' + '<a name="imports"></a>\n' +
|
||||
imports_content + '\n\n' + '<a name="dependencies"></a>\n' + dependencies_content
|
||||
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: combined_content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2 as json { Any }
|
||||
|
||||
// Tool specification for listing functions in a Rust file
|
||||
const list_functions_in_file_spec = mcp.Tool{
|
||||
name: 'list_functions_in_file'
|
||||
description: 'Lists all function definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_functions_in_file
|
||||
pub fn list_functions_in_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
result := rust.list_functions_in_file(file_path) or { return mcp.error_tool_call_result(err) }
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for listing structs in a Rust file
|
||||
const list_structs_in_file_spec = mcp.Tool{
|
||||
name: 'list_structs_in_file'
|
||||
description: 'Lists all struct definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_structs_in_file
|
||||
pub fn list_structs_in_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
result := rust.list_structs_in_file(file_path) or { return mcp.error_tool_call_result(err) }
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for listing modules in a directory
|
||||
const list_modules_in_dir_spec = mcp.Tool{
|
||||
name: 'list_modules_in_dir'
|
||||
description: 'Lists all Rust modules in a directory'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'dir_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the directory'
|
||||
})
|
||||
}
|
||||
required: ['dir_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_modules_in_dir
|
||||
pub fn list_modules_in_dir_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
dir_path := arguments['dir_path'].str()
|
||||
result := rust.list_modules_in_directory(dir_path) or { return mcp.error_tool_call_result(err) }
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for getting an import statement
|
||||
const get_import_statement_spec = mcp.Tool{
|
||||
name: 'get_import_statement'
|
||||
description: 'Generates appropriate Rust import statement for a module based on file paths'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'current_file': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the file where the import will be added'
|
||||
})
|
||||
'target_module': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the target module to be imported'
|
||||
})
|
||||
}
|
||||
required: ['current_file', 'target_module']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_import_statement
|
||||
pub fn get_import_statement_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
current_file := arguments['current_file'].str()
|
||||
target_module := arguments['target_module'].str()
|
||||
result := rust.generate_import_statement(current_file, target_module) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for getting module dependency information
|
||||
const get_module_dependency_spec = mcp.Tool{
|
||||
name: 'get_module_dependency'
|
||||
description: 'Gets dependency information for adding a Rust module to a project'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'importer_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the file that will import the module'
|
||||
})
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the module that will be imported'
|
||||
})
|
||||
}
|
||||
required: ['importer_path', 'module_path']
|
||||
}
|
||||
}
|
||||
|
||||
struct Tester {
|
||||
import_statement string
|
||||
module_path string
|
||||
}
|
||||
|
||||
// Handler for get_module_dependency
|
||||
pub fn get_module_dependency_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
importer_path := arguments['importer_path'].str()
|
||||
module_path := arguments['module_path'].str()
|
||||
dependency := rust.get_module_dependency(importer_path, module_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: result_to_mcp_tool_contents[Tester](Tester{
|
||||
import_statement: dependency.import_statement
|
||||
module_path: dependency.module_path
|
||||
}) // Return JSON string
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Function from File Tool ---
|
||||
|
||||
// Specification for get_function_from_file tool
|
||||
const get_function_from_file_spec = mcp.Tool{
|
||||
name: 'get_function_from_file'
|
||||
description: 'Get the declaration of a Rust function from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file.'
|
||||
})
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: "Name of the function to retrieve (e.g., 'my_function' or 'MyStruct::my_method')."
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_function_from_file
|
||||
pub fn get_function_from_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
function_name := arguments['function_name'].str()
|
||||
result := rust.get_function_from_file(file_path, function_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Function from Module Tool ---
|
||||
|
||||
// Specification for get_function_from_module tool
|
||||
const get_function_from_module_spec = mcp.Tool{
|
||||
name: 'get_function_from_module'
|
||||
description: 'Get the declaration of a Rust function from a specified module path (directory or file).'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust module directory or file.'
|
||||
})
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: "Name of the function to retrieve (e.g., 'my_function' or 'MyStruct::my_method')."
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_function_from_module
|
||||
pub fn get_function_from_module_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
module_path := arguments['module_path'].str()
|
||||
function_name := arguments['function_name'].str()
|
||||
result := rust.get_function_from_module(module_path, function_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Struct from File Tool ---
|
||||
|
||||
// Specification for get_struct_from_file tool
|
||||
const get_struct_from_file_spec = mcp.Tool{
|
||||
name: 'get_struct_from_file'
|
||||
description: 'Get the declaration of a Rust struct from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file.'
|
||||
})
|
||||
'struct_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: "Name of the struct to retrieve (e.g., 'MyStruct')."
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_struct_from_file
|
||||
pub fn get_struct_from_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
struct_name := arguments['struct_name'].str()
|
||||
result := rust.get_struct_from_file(file_path, struct_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Struct from Module Tool ---
|
||||
|
||||
// Specification for get_struct_from_module tool
|
||||
const get_struct_from_module_spec = mcp.Tool{
|
||||
name: 'get_struct_from_module'
|
||||
description: 'Get the declaration of a Rust struct from a specified module path (directory or file).'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust module directory or file.'
|
||||
})
|
||||
'struct_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: "Name of the struct to retrieve (e.g., 'MyStruct')."
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_struct_from_module
|
||||
pub fn get_struct_from_module_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
module_path := arguments['module_path'].str()
|
||||
struct_name := arguments['struct_name'].str()
|
||||
result := rust.get_struct_from_module(module_path, struct_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module mcp
|
||||
module mcpcore
|
||||
|
||||
import x.json2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module mcp
|
||||
module mcpcore
|
||||
|
||||
import time
|
||||
import os
|
||||
@@ -1,4 +1,4 @@
|
||||
module mcp
|
||||
module mcpcore
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
Reference in New Issue
Block a user