...
This commit is contained in:
@@ -5,25 +5,24 @@ module main
|
||||
import freeflowuniverse.herolib.clients.openai
|
||||
import os
|
||||
|
||||
|
||||
fn test1(mut client openai.OpenAI)!{
|
||||
|
||||
|
||||
instruction:='
|
||||
fn test1(mut client openai.OpenAI) ! {
|
||||
instruction := '
|
||||
You are a template language converter. You convert Pug templates to Jet templates.
|
||||
|
||||
The target template language, Jet, is defined as follows:
|
||||
'
|
||||
|
||||
// Create a chat completion request
|
||||
res := client.chat_completion(msgs:openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: 'What are the key differences between Groq and other AI inference providers?'
|
||||
},
|
||||
]
|
||||
})!
|
||||
res := client.chat_completion(
|
||||
msgs: openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: 'What are the key differences between Groq and other AI inference providers?'
|
||||
},
|
||||
]
|
||||
}
|
||||
)!
|
||||
|
||||
// Print the response
|
||||
println('\nGroq AI Response:')
|
||||
@@ -33,23 +32,21 @@ fn test1(mut client openai.OpenAI)!{
|
||||
println('Prompt tokens: ${res.usage.prompt_tokens}')
|
||||
println('Completion tokens: ${res.usage.completion_tokens}')
|
||||
println('Total tokens: ${res.usage.total_tokens}')
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn test2(mut client openai.OpenAI)!{
|
||||
|
||||
fn test2(mut client openai.OpenAI) ! {
|
||||
// Create a chat completion request
|
||||
res := client.chat_completion(
|
||||
model:"deepseek-r1-distill-llama-70b",
|
||||
msgs:openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: 'A story of 10 lines?'
|
||||
},
|
||||
]
|
||||
})!
|
||||
model: 'deepseek-r1-distill-llama-70b'
|
||||
msgs: openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: 'A story of 10 lines?'
|
||||
},
|
||||
]
|
||||
}
|
||||
)!
|
||||
|
||||
println('\nGroq AI Response:')
|
||||
println('==================')
|
||||
@@ -58,20 +55,17 @@ fn test2(mut client openai.OpenAI)!{
|
||||
println('Prompt tokens: ${res.usage.prompt_tokens}')
|
||||
println('Completion tokens: ${res.usage.completion_tokens}')
|
||||
println('Total tokens: ${res.usage.total_tokens}')
|
||||
|
||||
}
|
||||
|
||||
|
||||
println('
|
||||
println("
|
||||
TO USE:
|
||||
export AIKEY=\'gsk_...\'
|
||||
export AIURL=\'https://api.groq.com/openai/v1\'
|
||||
export AIMODEL=\'llama-3.3-70b-versatile\'
|
||||
')
|
||||
export AIKEY='gsk_...'
|
||||
export AIURL='https://api.groq.com/openai/v1'
|
||||
export AIMODEL='llama-3.3-70b-versatile'
|
||||
")
|
||||
|
||||
mut client:=openai.get(name:"test")!
|
||||
mut client := openai.get(name: 'test')!
|
||||
println(client)
|
||||
|
||||
|
||||
// test1(mut client)!
|
||||
test2(mut client)!
|
||||
|
||||
@@ -4,4 +4,4 @@ import freeflowuniverse.herolib.mcp.aitools
|
||||
|
||||
// aitools.convert_pug("/root/code/github/freeflowuniverse/herolauncher/pkg/herolauncher/web/templates/admin")!
|
||||
|
||||
aitools.convert_pug("/root/code/github/freeflowuniverse/herolauncher/pkg/zaz/webui/templates")!
|
||||
aitools.convert_pug('/root/code/github/freeflowuniverse/herolauncher/pkg/zaz/webui/templates')!
|
||||
|
||||
@@ -12,7 +12,7 @@ println('Starting Qdrant example script')
|
||||
println('Current directory: ${os.getwd()}')
|
||||
println('Home directory: ${os.home_dir()}')
|
||||
|
||||
mut i:=qdrant_installer.get()!
|
||||
mut i := qdrant_installer.get()!
|
||||
i.install()!
|
||||
|
||||
// 1. Get the qdrant client
|
||||
|
||||
@@ -6,5 +6,3 @@ import freeflowuniverse.herolib.web.docusaurus
|
||||
mut docs := docusaurus.new(
|
||||
build_path: '/tmp/docusaurus_build'
|
||||
)!
|
||||
|
||||
|
||||
|
||||
@@ -90,15 +90,14 @@ fn main() {
|
||||
'
|
||||
|
||||
mut docs := docusaurus.new(
|
||||
build_path: os.join_path(os.home_dir(), 'hero/var/docusaurus_demo1')
|
||||
update: true // Update the templates
|
||||
heroscript: hero_script
|
||||
build_path: os.join_path(os.home_dir(), 'hero/var/docusaurus_demo1')
|
||||
update: true // Update the templates
|
||||
heroscript: hero_script
|
||||
) or {
|
||||
eprintln('Error creating docusaurus factory with inline script: ${err}')
|
||||
exit(1)
|
||||
eprintln('Error creating docusaurus factory with inline script: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
|
||||
// Create a site directory if it doesn't exist
|
||||
site_path := os.join_path(os.home_dir(), 'hero/var/docusaurus_demo_src')
|
||||
os.mkdir_all(site_path) or {
|
||||
|
||||
@@ -6,35 +6,35 @@ import freeflowuniverse.herolib.clients.openai
|
||||
@[params]
|
||||
pub struct TaskParams {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// Create a new task
|
||||
pub fn new_task(params TaskParams) &Task {
|
||||
return &Task{
|
||||
name: params.name
|
||||
description: params.description
|
||||
unit_tasks: []
|
||||
current_result: ''
|
||||
}
|
||||
return &Task{
|
||||
name: params.name
|
||||
description: params.description
|
||||
unit_tasks: []
|
||||
current_result: ''
|
||||
}
|
||||
}
|
||||
|
||||
// Default model configurations
|
||||
pub fn default_base_model() ModelConfig {
|
||||
return ModelConfig{
|
||||
name: 'qwen2.5-7b-instruct'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 2000
|
||||
}
|
||||
return ModelConfig{
|
||||
name: 'qwen2.5-7b-instruct'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 2000
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_retry_model() ModelConfig {
|
||||
return ModelConfig{
|
||||
name: 'gpt-4'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 4000
|
||||
}
|
||||
return ModelConfig{
|
||||
name: 'gpt-4'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 4000
|
||||
}
|
||||
}
|
||||
@@ -5,59 +5,58 @@ import freeflowuniverse.herolib.clients.openai
|
||||
// ModelConfig defines the configuration for an AI model
|
||||
pub struct ModelConfig {
|
||||
pub mut:
|
||||
name string
|
||||
provider string
|
||||
temperature f32
|
||||
max_tokens int
|
||||
name string
|
||||
provider string
|
||||
temperature f32
|
||||
max_tokens int
|
||||
}
|
||||
|
||||
// Create model configs
|
||||
const claude_3_sonnet = escalayer.ModelConfig{
|
||||
name: 'anthropic/claude-3.7-sonnet'
|
||||
provider: 'anthropic'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
const claude_3_sonnet = ModelConfig{
|
||||
name: 'anthropic/claude-3.7-sonnet'
|
||||
provider: 'anthropic'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
}
|
||||
|
||||
const gpt4 = escalayer.ModelConfig{
|
||||
name: 'gpt-4'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
const gpt4 = ModelConfig{
|
||||
name: 'gpt-4'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
}
|
||||
|
||||
|
||||
// Call an AI model using OpenRouter
|
||||
fn call_ai_model(prompt string, model ModelConfig)! string {
|
||||
// Get OpenAI client (configured for OpenRouter)
|
||||
mut client := get_openrouter_client()!
|
||||
fn call_ai_model(prompt string, model ModelConfig) !string {
|
||||
// Get OpenAI client (configured for OpenRouter)
|
||||
mut client := get_openrouter_client()!
|
||||
|
||||
// Create the message for the AI
|
||||
mut m := openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .system
|
||||
content: 'You are a helpful assistant.'
|
||||
},
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: prompt
|
||||
}
|
||||
]
|
||||
}
|
||||
// Create the message for the AI
|
||||
mut m := openai.Messages{
|
||||
messages: [
|
||||
openai.Message{
|
||||
role: .system
|
||||
content: 'You are a helpful assistant.'
|
||||
},
|
||||
openai.Message{
|
||||
role: .user
|
||||
content: prompt
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Call the AI model
|
||||
res := client.chat_completion(
|
||||
msgs: m,
|
||||
model: model.name,
|
||||
temperature: model.temperature,
|
||||
max_completion_tokens: model.max_tokens
|
||||
)!
|
||||
// Call the AI model
|
||||
res := client.chat_completion(
|
||||
msgs: m
|
||||
model: model.name
|
||||
temperature: model.temperature
|
||||
max_completion_tokens: model.max_tokens
|
||||
)!
|
||||
|
||||
// Extract the response content
|
||||
if res.choices.len > 0 {
|
||||
return res.choices[0].message.content
|
||||
}
|
||||
// Extract the response content
|
||||
if res.choices.len > 0 {
|
||||
return res.choices[0].message.content
|
||||
}
|
||||
|
||||
return error('No response from AI model')
|
||||
return error('No response from AI model')
|
||||
}
|
||||
@@ -5,19 +5,18 @@ import freeflowuniverse.herolib.osal
|
||||
import os
|
||||
|
||||
// Get an OpenAI client configured for OpenRouter
|
||||
fn get_openrouter_client()! &openai.OpenAI {
|
||||
|
||||
fn get_openrouter_client() !&openai.OpenAI {
|
||||
osal.env_set(key: 'OPENROUTER_API_KEY', value: '')
|
||||
// Get API key from environment variable
|
||||
api_key := os.getenv('OPENROUTER_API_KEY')
|
||||
if api_key == '' {
|
||||
return error('OPENROUTER_API_KEY environment variable not set')
|
||||
}
|
||||
// Get API key from environment variable
|
||||
api_key := os.getenv('OPENROUTER_API_KEY')
|
||||
if api_key == '' {
|
||||
return error('OPENROUTER_API_KEY environment variable not set')
|
||||
}
|
||||
|
||||
// Create OpenAI client with OpenRouter base URL
|
||||
mut client := openai.get(
|
||||
name: 'openrouter'
|
||||
)!
|
||||
// Create OpenAI client with OpenRouter base URL
|
||||
mut client := openai.get(
|
||||
name: 'openrouter'
|
||||
)!
|
||||
|
||||
return client
|
||||
return client
|
||||
}
|
||||
@@ -5,53 +5,61 @@ import log
|
||||
// Task represents a complete AI task composed of multiple sequential unit tasks
|
||||
pub struct Task {
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
unit_tasks []UnitTask
|
||||
current_result string
|
||||
name string
|
||||
description string
|
||||
unit_tasks []UnitTask
|
||||
current_result string
|
||||
}
|
||||
|
||||
// UnitTaskParams defines the parameters for creating a new unit task
|
||||
@[params]
|
||||
pub struct UnitTaskParams {
|
||||
pub:
|
||||
name string
|
||||
prompt_function fn(string) string
|
||||
callback_function fn(string)! string
|
||||
base_model ?ModelConfig
|
||||
retry_model ?ModelConfig
|
||||
retry_count ?int
|
||||
name string
|
||||
prompt_function fn (string) string
|
||||
callback_function fn (string) !string
|
||||
base_model ?ModelConfig
|
||||
retry_model ?ModelConfig
|
||||
retry_count ?int
|
||||
}
|
||||
|
||||
// Add a new unit task to the task
|
||||
pub fn (mut t Task) new_unit_task(params UnitTaskParams) &UnitTask {
|
||||
mut unit_task := UnitTask{
|
||||
name: params.name
|
||||
prompt_function: params.prompt_function
|
||||
callback_function: params.callback_function
|
||||
base_model: if base_model := params.base_model { base_model } else { default_base_model() }
|
||||
retry_model: if retry_model := params.retry_model { retry_model } else { default_retry_model() }
|
||||
retry_count: if retry_count := params.retry_count { retry_count } else { 3 }
|
||||
}
|
||||
mut unit_task := UnitTask{
|
||||
name: params.name
|
||||
prompt_function: params.prompt_function
|
||||
callback_function: params.callback_function
|
||||
base_model: if base_model := params.base_model {
|
||||
base_model
|
||||
} else {
|
||||
default_base_model()
|
||||
}
|
||||
retry_model: if retry_model := params.retry_model {
|
||||
retry_model
|
||||
} else {
|
||||
default_retry_model()
|
||||
}
|
||||
retry_count: if retry_count := params.retry_count { retry_count } else { 3 }
|
||||
}
|
||||
|
||||
t.unit_tasks << unit_task
|
||||
return &t.unit_tasks[t.unit_tasks.len - 1]
|
||||
t.unit_tasks << unit_task
|
||||
return &t.unit_tasks[t.unit_tasks.len - 1]
|
||||
}
|
||||
|
||||
// Initiate the task execution
|
||||
pub fn (mut t Task) initiate(input string)! string {
|
||||
mut current_input := input
|
||||
pub fn (mut t Task) initiate(input string) !string {
|
||||
mut current_input := input
|
||||
|
||||
for i, mut unit_task in t.unit_tasks {
|
||||
log.error('Executing unit task ${i+1}/${t.unit_tasks.len}: ${unit_task.name}')
|
||||
for i, mut unit_task in t.unit_tasks {
|
||||
log.error('Executing unit task ${i + 1}/${t.unit_tasks.len}: ${unit_task.name}')
|
||||
|
||||
// Execute the unit task with the current input
|
||||
result := unit_task.execute(current_input)!
|
||||
// Execute the unit task with the current input
|
||||
result := unit_task.execute(current_input)!
|
||||
|
||||
// Update the current input for the next unit task
|
||||
current_input = result
|
||||
t.current_result = result
|
||||
}
|
||||
// Update the current input for the next unit task
|
||||
current_input = result
|
||||
t.current_result = result
|
||||
}
|
||||
|
||||
return t.current_result
|
||||
return t.current_result
|
||||
}
|
||||
@@ -6,66 +6,66 @@ import freeflowuniverse.herolib.clients.openai
|
||||
// UnitTask represents a single step in the task
|
||||
pub struct UnitTask {
|
||||
pub mut:
|
||||
name string
|
||||
prompt_function fn(string) string
|
||||
callback_function fn(string)! string
|
||||
base_model ModelConfig
|
||||
retry_model ModelConfig
|
||||
retry_count int
|
||||
name string
|
||||
prompt_function fn (string) string
|
||||
callback_function fn (string) !string
|
||||
base_model ModelConfig
|
||||
retry_model ModelConfig
|
||||
retry_count int
|
||||
}
|
||||
|
||||
// Execute the unit task
|
||||
pub fn (mut ut UnitTask) execute(input string)! string {
|
||||
// Generate the prompt using the prompt function
|
||||
prompt := ut.prompt_function(input)
|
||||
pub fn (mut ut UnitTask) execute(input string) !string {
|
||||
// Generate the prompt using the prompt function
|
||||
prompt := ut.prompt_function(input)
|
||||
|
||||
// Try with the base model first
|
||||
mut current_model := ut.base_model
|
||||
mut attempts := 0
|
||||
mut max_attempts := ut.retry_count + 1 // +1 for the initial attempt
|
||||
mut absolute_max_attempts := 1 // Hard limit on total attempts
|
||||
mut last_error := ''
|
||||
// Try with the base model first
|
||||
mut current_model := ut.base_model
|
||||
mut attempts := 0
|
||||
mut max_attempts := ut.retry_count + 1 // +1 for the initial attempt
|
||||
mut absolute_max_attempts := 1 // Hard limit on total attempts
|
||||
mut last_error := ''
|
||||
|
||||
for attempts < max_attempts && attempts < absolute_max_attempts {
|
||||
attempts++
|
||||
for attempts < max_attempts && attempts < absolute_max_attempts {
|
||||
attempts++
|
||||
|
||||
// If we've exhausted retries with the base model, switch to the retry model
|
||||
if attempts > ut.retry_count {
|
||||
log.error('Escalating to more powerful model: ${ut.retry_model.name}')
|
||||
current_model = ut.retry_model
|
||||
// Calculate remaining attempts but don't exceed absolute max
|
||||
max_attempts = attempts + ut.retry_count
|
||||
if max_attempts > absolute_max_attempts {
|
||||
max_attempts = absolute_max_attempts
|
||||
}
|
||||
}
|
||||
// If we've exhausted retries with the base model, switch to the retry model
|
||||
if attempts > ut.retry_count {
|
||||
log.error('Escalating to more powerful model: ${ut.retry_model.name}')
|
||||
current_model = ut.retry_model
|
||||
// Calculate remaining attempts but don't exceed absolute max
|
||||
max_attempts = attempts + ut.retry_count
|
||||
if max_attempts > absolute_max_attempts {
|
||||
max_attempts = absolute_max_attempts
|
||||
}
|
||||
}
|
||||
|
||||
log.error('Attempt ${attempts} with model ${current_model.name}')
|
||||
log.error('Attempt ${attempts} with model ${current_model.name}')
|
||||
|
||||
// Prepare the prompt with error feedback if this is a retry
|
||||
mut current_prompt := prompt
|
||||
if last_error != '' {
|
||||
current_prompt = 'Previous attempt failed with error: ${last_error}\n\n${prompt}'
|
||||
}
|
||||
// Prepare the prompt with error feedback if this is a retry
|
||||
mut current_prompt := prompt
|
||||
if last_error != '' {
|
||||
current_prompt = 'Previous attempt failed with error: ${last_error}\n\n${prompt}'
|
||||
}
|
||||
|
||||
// Call the AI model
|
||||
response := call_ai_model(current_prompt, current_model) or {
|
||||
log.error('AI call failed: ${err}')
|
||||
last_error = err.str()
|
||||
continue // Try again
|
||||
}
|
||||
// Call the AI model
|
||||
response := call_ai_model(current_prompt, current_model) or {
|
||||
log.error('AI call failed: ${err}')
|
||||
last_error = err.str()
|
||||
continue // Try again
|
||||
}
|
||||
|
||||
// Process the response with the callback function
|
||||
result := ut.callback_function(response) or {
|
||||
// If callback returns an error, retry with the error message
|
||||
log.error('Callback returned error: ${err}')
|
||||
last_error = err.str()
|
||||
continue // Try again
|
||||
}
|
||||
// Process the response with the callback function
|
||||
result := ut.callback_function(response) or {
|
||||
// If callback returns an error, retry with the error message
|
||||
log.error('Callback returned error: ${err}')
|
||||
last_error = err.str()
|
||||
continue // Try again
|
||||
}
|
||||
|
||||
// If we get here, the callback was successful
|
||||
return result
|
||||
}
|
||||
// If we get here, the callback was successful
|
||||
return result
|
||||
}
|
||||
|
||||
return error('Failed to execute unit task after ${attempts} attempts. Last error: ${last_error}')
|
||||
return error('Failed to execute unit task after ${attempts} attempts. Last error: ${last_error}')
|
||||
}
|
||||
@@ -114,16 +114,14 @@ fn (b &MemoryBackend) prompt_messages_get(name string, arguments map[string]stri
|
||||
return messages
|
||||
}
|
||||
|
||||
|
||||
fn (b &MemoryBackend) prompt_call(name string, arguments []string) ![]PromptMessage {
|
||||
// Get the tool handler
|
||||
handler := b.prompt_handlers[name] or { return error('tool handler not found') }
|
||||
|
||||
// Call the handler with the provided arguments
|
||||
return handler(arguments) or {panic(err)}
|
||||
return handler(arguments) or { panic(err) }
|
||||
}
|
||||
|
||||
|
||||
// Tool related methods
|
||||
|
||||
fn (b &MemoryBackend) tool_exists(name string) !bool {
|
||||
@@ -165,11 +163,11 @@ fn (b &MemoryBackend) sampling_create_message(params map[string]json2.Any) !Samp
|
||||
// Return a default implementation that just echoes back a message
|
||||
// indicating that no sampling handler is registered
|
||||
return SamplingCreateMessageResult{
|
||||
model: 'default'
|
||||
model: 'default'
|
||||
stop_reason: 'endTurn'
|
||||
role: 'assistant'
|
||||
content: MessageContent{
|
||||
typ: 'text'
|
||||
role: 'assistant'
|
||||
content: MessageContent{
|
||||
typ: 'text'
|
||||
text: 'Sampling is not configured on this server. Please register a sampling handler.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,157 +11,162 @@ import freeflowuniverse.herolib.baobab.specification
|
||||
//
|
||||
|
||||
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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,18 @@ module baobab
|
||||
|
||||
import cli
|
||||
|
||||
pub const command := cli.Command{
|
||||
sort_flags: true
|
||||
name: 'baobab'
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'baobab'
|
||||
// execute: cmd_mcpgen
|
||||
description: 'baobab command'
|
||||
commands: [
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Baobab server'
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
|
||||
@@ -13,18 +13,18 @@ pub fn new_mcp_server(v &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_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
|
||||
'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_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
|
||||
'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{
|
||||
|
||||
@@ -13,20 +13,20 @@ prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)')
|
||||
help_requested := fp.bool('help', `h`, false, 'Show help message')
|
||||
|
||||
if help_requested {
|
||||
println(fp.usage())
|
||||
exit(0)
|
||||
println(fp.usage())
|
||||
exit(0)
|
||||
}
|
||||
|
||||
additional_args := fp.finalize() or {
|
||||
eprintln(err)
|
||||
println(fp.usage())
|
||||
exit(1)
|
||||
eprintln(err)
|
||||
println(fp.usage())
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if additional_args.len > 0 {
|
||||
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
|
||||
println(fp.usage())
|
||||
exit(1)
|
||||
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
|
||||
println(fp.usage())
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Change to the mcp directory
|
||||
@@ -36,20 +36,20 @@ os.chdir(mcp_dir) or { panic('Failed to change directory to ${mcp_dir}: ${err}')
|
||||
// Set MCPPATH based on OS
|
||||
mut mcppath := '/usr/local/bin/mcp'
|
||||
if os.user_os() == 'macos' {
|
||||
mcppath = os.join_path(os.home_dir(), 'hero/bin/mcp')
|
||||
mcppath = os.join_path(os.home_dir(), 'hero/bin/mcp')
|
||||
}
|
||||
|
||||
// Set compilation command based on OS and mode
|
||||
compile_cmd := if prod_mode {
|
||||
'v -enable-globals -w -n -prod mcp.v'
|
||||
'v -enable-globals -w -n -prod mcp.v'
|
||||
} else {
|
||||
'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals mcp.v'
|
||||
'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals mcp.v'
|
||||
}
|
||||
|
||||
println('Building MCP in ${if prod_mode { 'production' } else { 'debug' }} mode...')
|
||||
|
||||
if os.system(compile_cmd) != 0 {
|
||||
panic('Failed to compile mcp.v with command: ${compile_cmd}')
|
||||
panic('Failed to compile mcp.v with command: ${compile_cmd}')
|
||||
}
|
||||
|
||||
// Make executable
|
||||
|
||||
@@ -45,7 +45,7 @@ mcp
|
||||
description: 'show verbose output'
|
||||
})
|
||||
|
||||
mut cmd_inspector := cli.Command{
|
||||
mut cmd_inspector := Command{
|
||||
sort_flags: true
|
||||
name: 'inspector'
|
||||
execute: cmd_inspector_execute
|
||||
@@ -68,7 +68,6 @@ mcp
|
||||
description: 'open inspector'
|
||||
})
|
||||
|
||||
|
||||
cmd_mcp.add_command(rhai_mcp.command)
|
||||
cmd_mcp.add_command(rust.command)
|
||||
// cmd_mcp.add_command(baobab.command)
|
||||
@@ -79,7 +78,7 @@ mcp
|
||||
cmd_mcp.parse(os.args)
|
||||
}
|
||||
|
||||
fn cmd_inspector_execute(cmd cli.Command) ! {
|
||||
fn cmd_inspector_execute(cmd Command) ! {
|
||||
open := cmd.flags.get_bool('open') or { false }
|
||||
if open {
|
||||
osal.exec(cmd: 'open http://localhost:5173')!
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module mcp
|
||||
|
||||
|
||||
pub fn result_to_mcp_tool_contents[T](result T) []ToolContent {
|
||||
return [result_to_mcp_tool_content[T](result)]
|
||||
}
|
||||
|
||||
@@ -110,7 +110,8 @@ fn (mut s Server) prompts_get_handler(data string) !string {
|
||||
// messages := s.backend.prompt_messages_get(request.params.name, request.params.arguments)!
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[PromptGetResult](request_map['id'].int(), PromptGetResult{
|
||||
response := jsonrpc.new_response_generic[PromptGetResult](request_map['id'].int(),
|
||||
PromptGetResult{
|
||||
description: prompt.description
|
||||
messages: messages
|
||||
})
|
||||
|
||||
@@ -30,9 +30,9 @@ pub:
|
||||
|
||||
pub struct ModelPreferences {
|
||||
pub:
|
||||
hints []ModelHint
|
||||
cost_priority f32 @[json: 'costPriority']
|
||||
speed_priority f32 @[json: 'speedPriority']
|
||||
hints []ModelHint
|
||||
cost_priority f32 @[json: 'costPriority']
|
||||
speed_priority f32 @[json: 'speedPriority']
|
||||
intelligence_priority f32 @[json: 'intelligencePriority']
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ pub:
|
||||
system_prompt string @[json: 'systemPrompt']
|
||||
include_context string @[json: 'includeContext']
|
||||
temperature f32
|
||||
max_tokens int @[json: 'maxTokens']
|
||||
stop_sequences []string @[json: 'stopSequences']
|
||||
max_tokens int @[json: 'maxTokens']
|
||||
stop_sequences []string @[json: 'stopSequences']
|
||||
metadata map[string]json2.Any
|
||||
}
|
||||
|
||||
@@ -131,11 +131,11 @@ fn parse_messages(messages_json json2.Any) ![]Message {
|
||||
}
|
||||
|
||||
result << Message{
|
||||
role: role
|
||||
role: role
|
||||
content: MessageContent{
|
||||
typ: typ
|
||||
text: text
|
||||
data: data
|
||||
typ: typ
|
||||
text: text
|
||||
data: data
|
||||
mimetype: mimetype
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ pub:
|
||||
|
||||
pub struct ToolItems {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
enum []string
|
||||
typ string @[json: 'type']
|
||||
enum []string
|
||||
properties map[string]ToolProperty
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fn (mut s Server) tools_list_handler(data string) !string {
|
||||
|
||||
// TODO: Implement pagination logic using the cursor
|
||||
// For now, return all tools
|
||||
encoded := json.encode(ToolListResult{
|
||||
encoded := json.encode(ToolListResult{
|
||||
tools: s.backend.tool_list()!
|
||||
next_cursor: '' // Empty if no more pages
|
||||
})
|
||||
|
||||
@@ -2,19 +2,18 @@ module mcpgen
|
||||
|
||||
import cli
|
||||
|
||||
pub const command := cli.Command{
|
||||
sort_flags: true
|
||||
name: 'mcpgen'
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'mcpgen'
|
||||
// execute: cmd_mcpgen
|
||||
description: 'will list existing mdbooks'
|
||||
commands: [
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the MCP server'
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
|
||||
@@ -7,7 +7,7 @@ import freeflowuniverse.herolib.schemas.jsonschema.codegen
|
||||
import os
|
||||
|
||||
pub struct FunctionPointer {
|
||||
name string // name of function
|
||||
name string // name of function
|
||||
module_path string // path to module
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct FunctionPointer {
|
||||
// returns an MCP Tool code in v for attaching the function to the mcp server
|
||||
// function_pointers: A list of function pointers to generate tools for
|
||||
pub fn (d &MCPGen) create_mcp_tools_code(function_pointers []FunctionPointer) !string {
|
||||
mut str := ""
|
||||
mut str := ''
|
||||
|
||||
for function_pointer in function_pointers {
|
||||
str += d.create_mcp_tool_code(function_pointer.name, function_pointer.module_path)!
|
||||
@@ -35,7 +35,6 @@ pub fn (d &MCPGen) create_mcp_tool_code(function_name string, module_path string
|
||||
return error('Failed to get function ${function_name} from module ${module_path}\n${err}')
|
||||
}
|
||||
|
||||
|
||||
mut types := map[string]string{}
|
||||
for param in function.params {
|
||||
// Check if the type is an Object (struct)
|
||||
@@ -45,7 +44,7 @@ pub fn (d &MCPGen) create_mcp_tool_code(function_name string, module_path string
|
||||
}
|
||||
|
||||
// Get the result type if it's a struct
|
||||
mut result_ := ""
|
||||
mut result_ := ''
|
||||
if function.result.typ is code.Result {
|
||||
result_type := (function.result.typ as code.Result).typ
|
||||
if result_type is code.Object {
|
||||
@@ -92,6 +91,7 @@ pub fn argument_decode_stmt(param code.Param) string {
|
||||
panic('Unsupported type: ${param.typ}')
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
in @generate_mcp.v , implement a create_mpc_tool_handler function that given a vlang function string and the types that map to their corresponding type definitions (for instance struct some_type: SomeType{...}), generates a vlang function such as the following:
|
||||
|
||||
@@ -103,7 +103,6 @@ pub fn (d &MCPGen) create_mcp_tool_tool_handler(arguments map[string]Any) !mcp.T
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// create_mcp_tool parses a V language function string and returns an MCP Tool struct
|
||||
// function: The V function string including preceding comments
|
||||
// types: A map of struct names to their definitions for complex parameter types
|
||||
@@ -139,15 +138,15 @@ pub fn (d MCPGen) create_mcp_tool(function code.Function, types map[string]strin
|
||||
|
||||
// Create the input schema
|
||||
input_schema := jsonschema.Schema{
|
||||
typ: 'object',
|
||||
properties: properties,
|
||||
required: required
|
||||
typ: 'object'
|
||||
properties: properties
|
||||
required: required
|
||||
}
|
||||
|
||||
// Create and return the Tool
|
||||
return mcp.Tool{
|
||||
name: function.name,
|
||||
description: function.description,
|
||||
name: function.name
|
||||
description: function.description
|
||||
input_schema: input_schema
|
||||
}
|
||||
}
|
||||
@@ -232,7 +231,6 @@ pub fn (d MCPGen) create_mcp_tool(function code.Function, types map[string]strin
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// parse_struct_fields parses a V language struct definition string and returns a map of field names to their types
|
||||
fn parse_struct_fields(struct_def string) map[string]string {
|
||||
mut fields := map[string]string{}
|
||||
|
||||
@@ -12,42 +12,41 @@ import x.json2 as json { Any }
|
||||
// function_pointers: A list of function pointers to generate tools for
|
||||
|
||||
const create_mcp_tools_code_tool = mcp.Tool{
|
||||
name: 'create_mcp_tools_code'
|
||||
description: 'create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists.
|
||||
name: 'create_mcp_tools_code'
|
||||
description: 'create_mcp_tool_code receives the name of a V language function string, and the path to the module in which it exists.
|
||||
returns an MCP Tool code in v for attaching the function to the mcp server
|
||||
function_pointers: A list of function pointers to generate tools for'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'function_pointers': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['name', 'module_path']
|
||||
}))
|
||||
})
|
||||
}
|
||||
required: ['function_pointers']
|
||||
}
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'function_pointers': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'array'
|
||||
items: jsonschema.Items(jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['name', 'module_path']
|
||||
}))
|
||||
})
|
||||
}
|
||||
required: ['function_pointers']
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (d &MCPGen) create_mcp_tools_code_tool_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
function_pointers := json.decode[[]FunctionPointer](arguments["function_pointers"].str())!
|
||||
result := d.create_mcp_tools_code(function_pointers)
|
||||
or {
|
||||
function_pointers := json.decode[[]FunctionPointer](arguments['function_pointers'].str())!
|
||||
result := d.create_mcp_tools_code(function_pointers) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +58,10 @@ returns an MCP Tool code in v for attaching the function to the mcp server'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
})
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['function_name', 'module_path']
|
||||
|
||||
@@ -12,16 +12,16 @@ pub fn new_mcp_server(v &MCPGen) !&mcp.Server {
|
||||
// Initialize the server with the empty handlers map
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'create_mcp_tool_code': create_mcp_tool_code_tool
|
||||
'create_mcp_tool_const': create_mcp_tool_const_tool
|
||||
'create_mcp_tool_code': create_mcp_tool_code_tool
|
||||
'create_mcp_tool_const': create_mcp_tool_const_tool
|
||||
'create_mcp_tool_handler': create_mcp_tool_handler_tool
|
||||
'create_mcp_tools_code': create_mcp_tools_code_tool
|
||||
'create_mcp_tools_code': create_mcp_tools_code_tool
|
||||
}
|
||||
tool_handlers: {
|
||||
'create_mcp_tool_code': v.create_mcp_tool_code_tool_handler
|
||||
'create_mcp_tool_const': v.create_mcp_tool_const_tool_handler
|
||||
'create_mcp_tool_code': v.create_mcp_tool_code_tool_handler
|
||||
'create_mcp_tool_const': v.create_mcp_tool_const_tool_handler
|
||||
'create_mcp_tool_handler': v.create_mcp_tool_handler_tool_handler
|
||||
'create_mcp_tools_code': v.create_mcp_tools_code_tool_handler
|
||||
'create_mcp_tools_code': v.create_mcp_tools_code_tool_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
|
||||
@@ -5,8 +5,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import json
|
||||
|
||||
pub fn convert_pug(mydir string)! {
|
||||
|
||||
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 {
|
||||
@@ -51,7 +50,7 @@ fn extract_template(raw_content string) string {
|
||||
return content
|
||||
}
|
||||
|
||||
pub fn convert_pug_file(myfile string)! {
|
||||
pub fn convert_pug_file(myfile string) ! {
|
||||
println(myfile)
|
||||
|
||||
// Create new file path by replacing .pug extension with .jet
|
||||
@@ -82,7 +81,6 @@ pub fn convert_pug_file(myfile string)! {
|
||||
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
|
||||
@@ -93,13 +91,13 @@ pub fn convert_pug_file(myfile string)! {
|
||||
for attempts < max_attempts && !is_valid {
|
||||
attempts++
|
||||
|
||||
mut system_content := texttools.dedent(base_instruction) + "\n" + l.jet()
|
||||
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
|
||||
user_prompt = texttools.dedent(base_user_prompt) + '\n' + content
|
||||
|
||||
// Print what we're sending to the AI service
|
||||
println('Sending to OpenAI for conversion:')
|
||||
@@ -127,9 +125,8 @@ Please fix the template and try again. Learn from feedback and check which jet t
|
||||
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
|
||||
|
||||
// Print what we're sending for the retry
|
||||
println('Sending to OpenAI for correction:')
|
||||
println('--------------------------------')
|
||||
println(user_prompt)
|
||||
@@ -146,12 +143,17 @@ Dont send back more information than the fixed template, make sure its in jet fo
|
||||
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)!
|
||||
res := client.chat_completion(
|
||||
msgs: m
|
||||
model: 'deepseek-r1-distill-llama-70b'
|
||||
max_completion_tokens: 64000
|
||||
)!
|
||||
|
||||
println("-----")
|
||||
println('-----')
|
||||
|
||||
// Print AI response before extraction
|
||||
println('Response received from AI:')
|
||||
@@ -188,13 +190,13 @@ Dont send back more information than the fixed template, make sure its in jet fo
|
||||
if is_valid {
|
||||
println('Successfully converted template after ${attempts} attempt(s)')
|
||||
// Create the file and write the processed content
|
||||
println("Converted to: ${jet_file}")
|
||||
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")
|
||||
jet_file2 := jet_file.replace('.jet', '_error.jet')
|
||||
mut jet_path2 := pathlib.get_file(path: jet_file2, create: true)!
|
||||
jet_path2.write(template)!
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ pub fn jetvaliditycheck(jetcontent string) !ValidationResult {
|
||||
|
||||
// Send the POST request to the validation endpoint
|
||||
req := httpconnection.Request{
|
||||
prefix: 'checkjet',
|
||||
data: template_data,
|
||||
prefix: 'checkjet'
|
||||
data: template_data
|
||||
dataformat: .json
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn jetvaliditycheck(jetcontent string) !ValidationResult {
|
||||
// Handle connection errors
|
||||
return ValidationResult{
|
||||
is_valid: false
|
||||
error: 'Connection error: ${err}'
|
||||
error: 'Connection error: ${err}'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,12 +58,12 @@ pub fn jetvaliditycheck(jetcontent string) !ValidationResult {
|
||||
// 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()}'
|
||||
error: 'Server returned unexpected format: ${err.msg()}'
|
||||
}
|
||||
}
|
||||
|
||||
// Use the structured response data
|
||||
if response.valid == false{
|
||||
if response.valid == false {
|
||||
error_msg := if response.error != '' {
|
||||
response.error
|
||||
} else if response.message != '' {
|
||||
@@ -74,12 +74,12 @@ pub fn jetvaliditycheck(jetcontent string) !ValidationResult {
|
||||
|
||||
return ValidationResult{
|
||||
is_valid: false
|
||||
error: error_msg
|
||||
error: error_msg
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult{
|
||||
is_valid: true
|
||||
error: ''
|
||||
error: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ pub mut:
|
||||
}
|
||||
|
||||
fn (mut loader FileLoader) load() {
|
||||
loader.embedded_files["jet"]=$embed_file('templates/jet_instructions.md')
|
||||
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") }
|
||||
c := loader.embedded_files['jet'] or { panic('bug embed') }
|
||||
return c.to_string()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,42 +13,42 @@ pub fn handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
if !os.exists(path) {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' does not exist")
|
||||
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 := ""
|
||||
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}")
|
||||
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") {
|
||||
} 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}")
|
||||
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")
|
||||
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)
|
||||
content: mcp.result_to_mcp_tool_contents[string](message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json { Any }
|
||||
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{
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string',
|
||||
typ: 'string'
|
||||
description: 'Path to a .pug file or directory containing .pug files to convert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,163 +4,175 @@ import freeflowuniverse.herolib.ai.mcp.aitools.escalayer
|
||||
import os
|
||||
|
||||
fn main() {
|
||||
// Get the current directory
|
||||
current_dir := os.dir(@FILE)
|
||||
// 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
|
||||
}
|
||||
// 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]
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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)
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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'
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping.md') or {
|
||||
println('Failed to read rhaiwrapping.md: ${err}')
|
||||
return
|
||||
}
|
||||
// Read the rhaiwrapping.md file
|
||||
rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.ourworld.tf/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
|
||||
}
|
||||
// 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}'
|
||||
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 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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]
|
||||
// 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 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
)
|
||||
// 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
|
||||
}
|
||||
// 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.ourworld.tf/herocode/sal/src/rhai for the compiled output.')
|
||||
println('Task completed successfully')
|
||||
println('The wrapper files have been generated and compiled in the target directory.')
|
||||
println('Check /Users/timurgordon/code/git.ourworld.tf/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}'
|
||||
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}'
|
||||
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}'
|
||||
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.ourworld.tf/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.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md') or { panic('Failed to read guides') }
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md') or { panic('Failed to read guides') }
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/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 := os.read_file('/Users/timurgordon/code/git.ourworld.tf/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.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md') or {
|
||||
panic('Failed to read guides')
|
||||
}
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/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}
|
||||
@@ -267,263 +279,254 @@ 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
|
||||
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'
|
||||
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}')
|
||||
}
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
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}')
|
||||
}
|
||||
// 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 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 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 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 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 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 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}')
|
||||
}
|
||||
}
|
||||
// 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
|
||||
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}'
|
||||
}
|
||||
// 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}'
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
// 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()
|
||||
// Extract the content between the markers
|
||||
content_start := start_index + start_marker.len
|
||||
content := response[content_start..end_index].trim_space()
|
||||
|
||||
return content
|
||||
return content
|
||||
}
|
||||
|
||||
// Extract module name from wrapper code
|
||||
fn extract_module_name(code string) string {
|
||||
lines := code.split('\n')
|
||||
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 ')
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
return ''
|
||||
}
|
||||
|
||||
struct RhaiGen {
|
||||
name string
|
||||
dir string
|
||||
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 ```')
|
||||
}
|
||||
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 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 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 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)
|
||||
// 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(", ")}')
|
||||
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
|
||||
}
|
||||
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}')
|
||||
}
|
||||
// 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 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 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')
|
||||
// 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}'
|
||||
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')
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
return functions
|
||||
}
|
||||
@@ -4,209 +4,204 @@ 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)
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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)
|
||||
// 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 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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.ourworld.tf/herocode/sal/src/rhai for the compiled output.')
|
||||
println('Task completed successfully')
|
||||
println('The wrapper files have been generated and compiled in the target directory.')
|
||||
println('Check /Users/timurgordon/code/git.ourworld.tf/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')
|
||||
}
|
||||
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]
|
||||
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.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}')
|
||||
}
|
||||
if !os.is_dir(source_code_path) {
|
||||
return error('Source code path is not a directory: ${source_code_path}')
|
||||
}
|
||||
|
||||
return 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}')
|
||||
}
|
||||
// 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)
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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'
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
if source_code == '' {
|
||||
return error('No Rust files found in directory: ${source_code_path}')
|
||||
}
|
||||
|
||||
return source_code
|
||||
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')
|
||||
}
|
||||
// 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}'
|
||||
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]
|
||||
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 { '' }
|
||||
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 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
)
|
||||
// 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('')
|
||||
// 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.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')
|
||||
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')
|
||||
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
// Load all required template and guide files
|
||||
guides := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')
|
||||
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')
|
||||
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/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)
|
||||
// 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 ''
|
||||
}
|
||||
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.
|
||||
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}
|
||||
@@ -313,305 +308,289 @@ 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
|
||||
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'
|
||||
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}')
|
||||
}
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
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}')
|
||||
}
|
||||
// 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 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 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 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 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 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 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}')
|
||||
}
|
||||
}
|
||||
// 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
|
||||
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}'
|
||||
}
|
||||
// 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}'
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
// 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()
|
||||
// Extract the content between the markers
|
||||
content_start := start_index + start_marker.len
|
||||
content := response[content_start..end_index].trim_space()
|
||||
|
||||
return content
|
||||
return content
|
||||
}
|
||||
|
||||
// Extract module name from wrapper code
|
||||
fn extract_module_name(code string) string {
|
||||
lines := code.split('\n')
|
||||
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 ')
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
return ''
|
||||
}
|
||||
|
||||
// RhaiGen struct for generating Rhai wrappers
|
||||
struct RhaiGen {
|
||||
name string
|
||||
dir string
|
||||
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
|
||||
}
|
||||
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)
|
||||
// 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(", ")}')
|
||||
println('Using module name: ${gen.name}_rhai')
|
||||
println('Extracted functions: ${functions.join(', ')}')
|
||||
|
||||
name := gen.name
|
||||
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 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}')
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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)
|
||||
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
|
||||
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 ```')
|
||||
}
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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}')
|
||||
}
|
||||
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')
|
||||
}
|
||||
// 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
|
||||
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}')
|
||||
}
|
||||
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 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')
|
||||
// Run the example
|
||||
run_result := os.execute('cargo run --example example')
|
||||
|
||||
return build_result.output, run_result.output
|
||||
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}'
|
||||
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')
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
return functions
|
||||
}
|
||||
@@ -6,285 +6,278 @@ 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
|
||||
})!
|
||||
// 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 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
)
|
||||
// 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('')
|
||||
// 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')!
|
||||
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.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')!
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')!
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
engine := $tmpl('./prompts/engine.md')
|
||||
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
|
||||
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')!
|
||||
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')!
|
||||
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
|
||||
|
||||
prompt := $tmpl('./prompts/main.md')
|
||||
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
|
||||
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 {
|
||||
|
||||
pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string) !string {
|
||||
// Define project directory paths
|
||||
project_dir := '${path}/rhai'
|
||||
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}')
|
||||
}
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
// 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}')
|
||||
}
|
||||
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}')
|
||||
}
|
||||
// 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 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 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 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 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 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 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}')
|
||||
}
|
||||
// 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
|
||||
return project_dir
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Extract module name from wrapper code
|
||||
fn extract_module_name(code string) string {
|
||||
lines := code.split('\n')
|
||||
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 ')
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
return ''
|
||||
}
|
||||
|
||||
// RhaiGen struct for generating Rhai wrappers
|
||||
struct RhaiGen {
|
||||
name string
|
||||
dir string
|
||||
source_pkg_info rust.SourcePackageInfo
|
||||
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
|
||||
}
|
||||
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)!
|
||||
// Write the module files
|
||||
project_dir := write_rhai_wrapper_module(mod, gen.name, gen.dir)!
|
||||
|
||||
return project_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
|
||||
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 ```')
|
||||
}
|
||||
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 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 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 ```')
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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}'
|
||||
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}'
|
||||
}
|
||||
|
||||
@@ -144,8 +144,6 @@ import os
|
||||
// return project_dir
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // Extract module name from wrapper code
|
||||
// fn extract_module_name(code string) string {
|
||||
// lines := code.split('\n')
|
||||
|
||||
@@ -2,17 +2,17 @@ module mcp
|
||||
|
||||
import cli
|
||||
|
||||
pub const command := cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rhai'
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rhai'
|
||||
// execute: cmd_mcpgen
|
||||
description: 'rhai command'
|
||||
commands: [
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Rhai server'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,4 +20,3 @@ fn cmd_start(cmd cli.Command) ! {
|
||||
mut server := new_mcp_server()!
|
||||
server.start()!
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ pub fn new_mcp_server() !&mcp.Server {
|
||||
|
||||
// Initialize the server with the empty handlers map
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
tools: {
|
||||
'generate_rhai_wrapper': generate_rhai_wrapper_spec
|
||||
}
|
||||
tool_handlers: {
|
||||
tool_handlers: {
|
||||
'generate_rhai_wrapper': generate_rhai_wrapper_handler
|
||||
}
|
||||
prompts: {
|
||||
|
||||
@@ -5,39 +5,41 @@ 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 { Any }
|
||||
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
|
||||
}
|
||||
]
|
||||
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)!
|
||||
// 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)
|
||||
// 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)!
|
||||
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
|
||||
}
|
||||
}]
|
||||
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,19 +1,19 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json { Any }
|
||||
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{
|
||||
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',
|
||||
typ: 'string'
|
||||
description: 'Path to a .rs file or directory containing .rs files to make rhai interface for'
|
||||
})
|
||||
}
|
||||
required: ['path']
|
||||
|
||||
@@ -8,32 +8,31 @@ 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']
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ module rust
|
||||
|
||||
import cli
|
||||
|
||||
pub const command := cli.Command{
|
||||
pub const command = cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rust'
|
||||
description: 'Rust language tools command'
|
||||
commands: [
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Rust MCP server'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp {ToolContent}
|
||||
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)]
|
||||
|
||||
@@ -9,40 +9,40 @@ pub fn new_mcp_server() !&mcp.Server {
|
||||
|
||||
// Initialize the server with tools and prompts
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
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
|
||||
'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: {
|
||||
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
|
||||
'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
|
||||
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
|
||||
'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_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
|
||||
'rust_tools_guide': rust_tools_guide_prompt_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'rust'
|
||||
name: 'rust'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,113 +2,123 @@ module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import os
|
||||
import x.json2 as json { Any }
|
||||
import x.json2 as json
|
||||
|
||||
// Prompt specification for Rust functions
|
||||
const rust_functions_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_functions'
|
||||
name: 'rust_functions'
|
||||
description: 'Provides guidance on working with Rust functions and using the list_functions_in_file tool'
|
||||
arguments: []
|
||||
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
|
||||
}
|
||||
}]
|
||||
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'
|
||||
name: 'rust_structs'
|
||||
description: 'Provides guidance on working with Rust structs and using the list_structs_in_file tool'
|
||||
arguments: []
|
||||
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
|
||||
}
|
||||
}]
|
||||
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'
|
||||
name: 'rust_modules'
|
||||
description: 'Provides guidance on working with Rust modules and using the list_modules_in_dir tool'
|
||||
arguments: []
|
||||
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
|
||||
}
|
||||
}]
|
||||
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'
|
||||
name: 'rust_imports'
|
||||
description: 'Provides guidance on working with Rust imports and using the get_import_statement tool'
|
||||
arguments: []
|
||||
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
|
||||
}
|
||||
}]
|
||||
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'
|
||||
name: 'rust_dependencies'
|
||||
description: 'Provides guidance on working with Rust dependencies and using the get_module_dependency tool'
|
||||
arguments: []
|
||||
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
|
||||
}
|
||||
}]
|
||||
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'
|
||||
name: 'rust_tools_guide'
|
||||
description: 'Provides a comprehensive guide on all available Rust tools and how to use them'
|
||||
arguments: []
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_tools_guide prompt
|
||||
@@ -122,23 +132,20 @@ pub fn rust_tools_guide_prompt_handler(arguments []string) ![]mcp.PromptMessage
|
||||
|
||||
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
|
||||
'## 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
|
||||
}
|
||||
}]
|
||||
return [
|
||||
mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: combined_content
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,111 +1,105 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp {ToolContent}
|
||||
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'
|
||||
name: 'list_functions_in_file'
|
||||
description: 'Lists all function definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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'
|
||||
name: 'list_structs_in_file'
|
||||
description: 'Lists all struct definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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'
|
||||
name: 'list_modules_in_dir'
|
||||
description: 'Lists all Rust modules in a directory'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'dir_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'Path to the directory'
|
||||
})
|
||||
}
|
||||
required: ['dir_path']
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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'
|
||||
name: 'get_import_statement'
|
||||
description: 'Generates appropriate Rust import statement for a module based on file paths'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'current_file': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'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'
|
||||
typ: 'string'
|
||||
description: 'Path to the target module to be imported'
|
||||
})
|
||||
}
|
||||
required: ['current_file', 'target_module']
|
||||
required: ['current_file', 'target_module']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,33 +112,33 @@ pub fn get_import_statement_handler(arguments map[string]Any) !mcp.ToolCallResul
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
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'
|
||||
name: 'get_module_dependency'
|
||||
description: 'Gets dependency information for adding a Rust module to a project'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'importer_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'Path to the file that will import the module'
|
||||
}),
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the module that will be imported'
|
||||
})
|
||||
}
|
||||
required: ['importer_path', 'module_path']
|
||||
required: ['importer_path', 'module_path']
|
||||
}
|
||||
}
|
||||
|
||||
struct Tester {
|
||||
import_statement string
|
||||
module_path string
|
||||
module_path string
|
||||
}
|
||||
|
||||
// Handler for get_module_dependency
|
||||
@@ -157,9 +151,9 @@ pub fn get_module_dependency_handler(arguments map[string]Any) !mcp.ToolCallResu
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: result_to_mcp_tool_contents[Tester](Tester{
|
||||
content: result_to_mcp_tool_contents[Tester](Tester{
|
||||
import_statement: dependency.import_statement
|
||||
module_path: dependency.module_path
|
||||
module_path: dependency.module_path
|
||||
}) // Return JSON string
|
||||
}
|
||||
}
|
||||
@@ -168,21 +162,21 @@ pub fn get_module_dependency_handler(arguments map[string]Any) !mcp.ToolCallResu
|
||||
|
||||
// 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.'
|
||||
name: 'get_function_from_file'
|
||||
description: 'Get the declaration of a Rust function from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'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\').'
|
||||
typ: 'string'
|
||||
description: "Name of the function to retrieve (e.g., 'my_function' or 'MyStruct::my_method')."
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'function_name']
|
||||
required: ['file_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +189,7 @@ pub fn get_function_from_file_handler(arguments map[string]Any) !mcp.ToolCallRes
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,21 +197,21 @@ pub fn get_function_from_file_handler(arguments map[string]Any) !mcp.ToolCallRes
|
||||
|
||||
// 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).'
|
||||
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'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'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\').'
|
||||
typ: 'string'
|
||||
description: "Name of the function to retrieve (e.g., 'my_function' or 'MyStruct::my_method')."
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'function_name']
|
||||
required: ['module_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +224,7 @@ pub fn get_function_from_module_handler(arguments map[string]Any) !mcp.ToolCallR
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,21 +232,21 @@ pub fn get_function_from_module_handler(arguments map[string]Any) !mcp.ToolCallR
|
||||
|
||||
// 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.'
|
||||
name: 'get_struct_from_file'
|
||||
description: 'Get the declaration of a Rust struct from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'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\').'
|
||||
typ: 'string'
|
||||
description: "Name of the struct to retrieve (e.g., 'MyStruct')."
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'struct_name']
|
||||
required: ['file_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +259,7 @@ pub fn get_struct_from_file_handler(arguments map[string]Any) !mcp.ToolCallResul
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,21 +267,21 @@ pub fn get_struct_from_file_handler(arguments map[string]Any) !mcp.ToolCallResul
|
||||
|
||||
// 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).'
|
||||
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'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
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\').'
|
||||
typ: 'string'
|
||||
description: "Name of the struct to retrieve (e.g., 'MyStruct')."
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'struct_name']
|
||||
required: ['module_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +294,6 @@ pub fn get_struct_from_module_handler(arguments map[string]Any) !mcp.ToolCallRes
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ pub fn new_mcp_server(v &VCode) !&mcp.Server {
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'get_function_from_file': get_function_from_file_tool
|
||||
'write_vfile': write_vfile_tool
|
||||
'write_vfile': write_vfile_tool
|
||||
}
|
||||
tool_handlers: {
|
||||
'get_function_from_file': v.get_function_from_file_tool_handler
|
||||
'write_vfile': v.write_vfile_tool_handler
|
||||
'write_vfile': v.write_vfile_tool_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
|
||||
@@ -3,7 +3,7 @@ module vcode
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2 {Any}
|
||||
import x.json2 { Any }
|
||||
|
||||
const get_function_from_file_tool = mcp.Tool{
|
||||
name: 'get_function_from_file'
|
||||
@@ -16,10 +16,10 @@ RETURNS: string - the function block including comments, or empty string if not
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
})
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'function_name']
|
||||
|
||||
@@ -3,7 +3,7 @@ module vcode
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2 {Any}
|
||||
import x.json2 { Any }
|
||||
|
||||
const write_vfile_tool = mcp.Tool{
|
||||
name: 'write_vfile'
|
||||
@@ -18,20 +18,20 @@ RETURNS: string - success message with the path of the written file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'code': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'code': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
'format': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'boolean'
|
||||
'format': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'boolean'
|
||||
})
|
||||
'overwrite': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'boolean'
|
||||
typ: 'boolean'
|
||||
})
|
||||
'prefix': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
'prefix': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
}
|
||||
required: ['path', 'code']
|
||||
@@ -49,23 +49,19 @@ pub fn (d &VCode) write_vfile_tool_handler(arguments map[string]Any) !mcp.ToolCa
|
||||
|
||||
// Create write options
|
||||
options := code.WriteOptions{
|
||||
format: format
|
||||
format: format
|
||||
overwrite: overwrite
|
||||
prefix: prefix
|
||||
prefix: prefix
|
||||
}
|
||||
|
||||
// Parse the V code string into a VFile
|
||||
vfile := code.parse_vfile(code_str) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
vfile := code.parse_vfile(code_str) or { return mcp.error_tool_call_result(err) }
|
||||
|
||||
// Write the VFile to the specified path
|
||||
vfile.write(path, options) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
vfile.write(path, options) or { return mcp.error_tool_call_result(err) }
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string]('Successfully wrote V file to ${path}')
|
||||
content: mcp.result_to_mcp_tool_contents[string]('Successfully wrote V file to ${path}')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,42 +13,42 @@ pub fn handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
if !os.exists(path) {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' does not exist")
|
||||
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 := ""
|
||||
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}")
|
||||
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(".v") {
|
||||
} else if path.ends_with('.v') {
|
||||
// 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}")
|
||||
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")
|
||||
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)
|
||||
content: mcp.result_to_mcp_tool_contents[string](message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
module pugconvert
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import x.json2 as json { Any }
|
||||
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{
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string',
|
||||
typ: 'string'
|
||||
description: 'Path to a .pug file or directory containing .pug files to convert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,33 +2,29 @@ module utils
|
||||
|
||||
// Helper function to extract code blocks from the response
|
||||
pub 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}'
|
||||
}
|
||||
// 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}'
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
}
|
||||
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 ''
|
||||
}
|
||||
// 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()
|
||||
// Extract the content between the markers
|
||||
content_start := start_index + start_marker.len
|
||||
content := response[content_start..end_index].trim_space()
|
||||
|
||||
return content
|
||||
return content
|
||||
}
|
||||
@@ -11,8 +11,7 @@ pub fn generate_module_from_openapi(openapi_path string) !string {
|
||||
openapi_spec := openapi.new(path: openapi_path)!
|
||||
actor_spec := specification.from_openapi(openapi_spec)!
|
||||
|
||||
actor_module := generator.generate_actor_module(
|
||||
actor_spec,
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi, .http]
|
||||
)!
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Array, CodeItem, Function, Import, Param, Result, Struct, VFile }
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, Function, Import, Param, Result, Struct, VFile }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
@@ -22,8 +22,9 @@ pub fn generate_methods_file_str(source Source) !string {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
else { panic('No openapi or openrpc path provided') }
|
||||
return generate_methods_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ pub fn generate_methods_example_file_str(source Source) !string {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
else { panic('No openapi or openrpc path provided') }
|
||||
return generate_methods_example_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ pub fn generate_methods_interface_file_str(source Source) !string {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
else { panic('No openapi or openrpc path provided') }
|
||||
return generate_methods_interface_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ pub fn generate_model_file_str(source Source) !string {
|
||||
specification.from_openapi(openapi.new(path: path)!)!
|
||||
} else if path := source.openrpc_path {
|
||||
specification.from_openrpc(openrpc.new(path: path)!)!
|
||||
} else {
|
||||
panic('No openapi or openrpc path provided')
|
||||
}
|
||||
else { panic('No openapi or openrpc path provided') }
|
||||
return generate_model_file(actor_spec)!.write_str()!
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ module specification
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.code { Struct }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.openapi { MediaType, OpenAPI, Parameter, Operation, OperationInfo }
|
||||
import freeflowuniverse.herolib.schemas.openapi { MediaType, OpenAPI, OperationInfo, Parameter }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, ErrorSpec, Example, ExamplePairing, ExampleRef }
|
||||
|
||||
// Helper function: Convert OpenAPI parameter to ContentDescriptor
|
||||
|
||||
@@ -114,7 +114,7 @@ fn (mut f OpenAI) create_audio_request(args AudioArgs, endpoint string) !AudioRe
|
||||
@[params]
|
||||
pub struct CreateSpeechArgs {
|
||||
pub:
|
||||
model string = "tts_1"
|
||||
model string = 'tts_1'
|
||||
input string @[required]
|
||||
voice Voice = .alloy
|
||||
response_format AudioFormat = .mp3
|
||||
|
||||
@@ -9,9 +9,9 @@ fn test_chat_completion() {
|
||||
|
||||
println(client.list_models()!)
|
||||
|
||||
raise("sss")
|
||||
raise('sss')
|
||||
|
||||
res := client.chat_completion( Messages{
|
||||
res := client.chat_completion(Messages{
|
||||
messages: [
|
||||
Message{
|
||||
role: .user
|
||||
|
||||
@@ -44,31 +44,31 @@ pub mut:
|
||||
|
||||
struct ChatMessagesRaw {
|
||||
mut:
|
||||
model string
|
||||
messages []MessageRaw
|
||||
temperature f64 = 0.5
|
||||
model string
|
||||
messages []MessageRaw
|
||||
temperature f64 = 0.5
|
||||
max_completion_tokens int = 32000
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct CompletionArgs{
|
||||
pub struct CompletionArgs {
|
||||
pub mut:
|
||||
model string
|
||||
msgs Messages
|
||||
temperature f64 = 0.5
|
||||
model string
|
||||
msgs Messages
|
||||
temperature f64 = 0.5
|
||||
max_completion_tokens int = 32000
|
||||
}
|
||||
|
||||
// creates a new chat completion given a list of messages
|
||||
// each message consists of message content and the role of the author
|
||||
pub fn (mut f OpenAI) chat_completion(args_ CompletionArgs) !ChatCompletion {
|
||||
mut args:=args_
|
||||
if args.model==""{
|
||||
mut args := args_
|
||||
if args.model == '' {
|
||||
args.model = f.model_default
|
||||
}
|
||||
mut m := ChatMessagesRaw{
|
||||
model: args.model
|
||||
temperature: args.temperature
|
||||
model: args.model
|
||||
temperature: args.temperature
|
||||
max_completion_tokens: args.max_completion_tokens
|
||||
}
|
||||
for msg in args.msgs.messages {
|
||||
|
||||
@@ -28,7 +28,9 @@ fn args_get(args_ ArgsGet) ArgsGet {
|
||||
pub fn get(args_ ArgsGet) !&OpenAI {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := OpenAI{name:args.name}
|
||||
mut obj := OpenAI{
|
||||
name: args.name
|
||||
}
|
||||
if args.name !in openai_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
|
||||
@@ -22,44 +22,43 @@ const default = true
|
||||
@[heap]
|
||||
pub struct OpenAI {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
api_key string
|
||||
url string
|
||||
name string = 'default'
|
||||
api_key string
|
||||
url string
|
||||
model_default string
|
||||
conn ?&httpconnection.HTTPConnection @[skip; str: skip]
|
||||
conn ?&httpconnection.HTTPConnection @[skip; str: skip]
|
||||
}
|
||||
|
||||
// your checking & initialization code if needed
|
||||
fn obj_init(mycfg_ OpenAI) !OpenAI {
|
||||
mut mycfg := mycfg_
|
||||
if mycfg.api_key==""{
|
||||
|
||||
mut k:=os.getenv('AIKEY')
|
||||
if k != ""{
|
||||
mycfg.api_key = k
|
||||
k=os.getenv('AIURL')
|
||||
if k != ""{
|
||||
if mycfg.api_key == '' {
|
||||
mut k := os.getenv('AIKEY')
|
||||
if k != '' {
|
||||
mycfg.api_key = k
|
||||
k = os.getenv('AIURL')
|
||||
if k != '' {
|
||||
mycfg.url = k
|
||||
}else{
|
||||
return error("found AIKEY in env, but not AIURL")
|
||||
}
|
||||
k=os.getenv('AIMODEL')
|
||||
if k != ""{
|
||||
} else {
|
||||
return error('found AIKEY in env, but not AIURL')
|
||||
}
|
||||
k = os.getenv('AIMODEL')
|
||||
if k != '' {
|
||||
mycfg.model_default = k
|
||||
}
|
||||
return mycfg
|
||||
}
|
||||
return mycfg
|
||||
}
|
||||
mycfg.url = "https://api.openai.com/v1/models"
|
||||
k=os.getenv('OPENAI_API_KEY')
|
||||
if k != ""{
|
||||
mycfg.api_key = k
|
||||
return mycfg
|
||||
mycfg.url = 'https://api.openai.com/v1/models'
|
||||
k = os.getenv('OPENAI_API_KEY')
|
||||
if k != '' {
|
||||
mycfg.api_key = k
|
||||
return mycfg
|
||||
}
|
||||
k=os.getenv('OPENROUTER_API_KEY')
|
||||
if k != ""{
|
||||
mycfg.api_key = k
|
||||
mycfg.url = "https://openrouter.ai/api/v1"
|
||||
return mycfg
|
||||
k = os.getenv('OPENROUTER_API_KEY')
|
||||
if k != '' {
|
||||
mycfg.api_key = k
|
||||
mycfg.url = 'https://openrouter.ai/api/v1'
|
||||
return mycfg
|
||||
}
|
||||
}
|
||||
return mycfg
|
||||
@@ -75,12 +74,12 @@ pub fn (mut client OpenAI) connection() !&httpconnection.HTTPConnection {
|
||||
)!
|
||||
c2
|
||||
}
|
||||
|
||||
c.default_header.set(.authorization, 'Bearer ${client.api_key}')
|
||||
client.conn = c
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
pub fn heroscript_dumps(obj OpenAI) !string {
|
||||
|
||||
@@ -6,9 +6,9 @@ import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
|
||||
pub interface IFile {
|
||||
name string
|
||||
write(string, WriteOptions) !
|
||||
write_str(WriteOptions) !string
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
@@ -124,7 +124,9 @@ pub fn (code VFile) write_str(options WriteOptions) !string {
|
||||
''
|
||||
}
|
||||
|
||||
mod_stmt := if code.mod == '' {''} else {
|
||||
mod_stmt := if code.mod == '' {
|
||||
''
|
||||
} else {
|
||||
'module ${code.mod}'
|
||||
}
|
||||
|
||||
@@ -210,8 +212,8 @@ pub fn parse_vfile(code string) !VFile {
|
||||
}
|
||||
|
||||
// Check for struct or function start
|
||||
if (trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ') ||
|
||||
trimmed.starts_with('fn ') || trimmed.starts_with('pub fn ')) && !in_struct_or_fn {
|
||||
if (trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ')
|
||||
|| trimmed.starts_with('fn ') || trimmed.starts_with('pub fn ')) && !in_struct_or_fn {
|
||||
in_struct_or_fn = true
|
||||
current_chunk = comment_block.join('\n')
|
||||
if current_chunk != '' {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module code
|
||||
|
||||
fn test_parse_vfile() {
|
||||
code := '
|
||||
code := "
|
||||
module test
|
||||
|
||||
import os
|
||||
@@ -9,7 +9,7 @@ import strings
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
const (
|
||||
VERSION = \'1.0.0\'
|
||||
VERSION = '1.0.0'
|
||||
DEBUG = true
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ pub mut:
|
||||
|
||||
// greet returns a greeting message
|
||||
pub fn (p Person) greet() string {
|
||||
return \'Hello, my name is \${p.name} and I am \${p.age} years old\'
|
||||
return 'Hello, my name is \${p.name} and I am \${p.age} years old'
|
||||
}
|
||||
|
||||
// create_person creates a new Person instance
|
||||
@@ -31,7 +31,7 @@ pub fn create_person(name string, age int) Person {
|
||||
age: age
|
||||
}
|
||||
}
|
||||
'
|
||||
"
|
||||
|
||||
vfile := parse_vfile(code) or {
|
||||
assert false, 'Failed to parse VFile: ${err}'
|
||||
@@ -50,7 +50,7 @@ pub fn create_person(name string, age int) Person {
|
||||
// Test constants
|
||||
assert vfile.consts.len == 2
|
||||
assert vfile.consts[0].name == 'VERSION'
|
||||
assert vfile.consts[0].value == '\'1.0.0\''
|
||||
assert vfile.consts[0].value == "'1.0.0'"
|
||||
assert vfile.consts[1].name == 'DEBUG'
|
||||
assert vfile.consts[1].value == 'true'
|
||||
|
||||
|
||||
@@ -150,13 +150,13 @@ pub fn parse_function(code_ string) !Function {
|
||||
description := comment_lines.join('\n')
|
||||
|
||||
return Function{
|
||||
name: name
|
||||
receiver: receiver
|
||||
params: params
|
||||
result: result
|
||||
body: body
|
||||
name: name
|
||||
receiver: receiver
|
||||
params: params
|
||||
result: result
|
||||
body: body
|
||||
description: description
|
||||
is_pub: is_pub
|
||||
has_return: has_return
|
||||
is_pub: is_pub
|
||||
has_return: has_return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,20 @@ module code
|
||||
|
||||
fn test_parse_function_with_comments() {
|
||||
// Test function string with comments
|
||||
function_str := '// test_function is a simple function for testing the MCP tool code generation
|
||||
function_str := "// test_function is a simple function for testing the MCP tool code generation
|
||||
// It takes a config and returns a result
|
||||
pub fn test_function(config TestConfig) !TestResult {
|
||||
// This is just a mock implementation for testing purposes
|
||||
if config.name == \'\' {
|
||||
return error(\'Name cannot be empty\')
|
||||
if config.name == '' {
|
||||
return error('Name cannot be empty')
|
||||
}
|
||||
|
||||
return TestResult{
|
||||
success: config.enabled
|
||||
message: \'Test completed for \${config.name}\'
|
||||
message: 'Test completed for \${config.name}'
|
||||
code: if config.enabled { 0 } else { 1 }
|
||||
}
|
||||
}'
|
||||
}"
|
||||
|
||||
// Parse the function
|
||||
function := parse_function(function_str) or {
|
||||
@@ -41,9 +41,9 @@ It takes a config and returns a result'
|
||||
|
||||
fn test_parse_function_without_comments() {
|
||||
// Test function string without comments
|
||||
function_str := 'fn simple_function(name string, count int) string {
|
||||
return \'\${name} count: \${count}\'
|
||||
}'
|
||||
function_str := "fn simple_function(name string, count int) string {
|
||||
return '\${name} count: \${count}'
|
||||
}"
|
||||
|
||||
// Parse the function
|
||||
function := parse_function(function_str) or {
|
||||
|
||||
@@ -69,7 +69,8 @@ pub fn parse_struct(code_ string) !Struct {
|
||||
trimmed := line.trim_space()
|
||||
if !in_struct && trimmed.starts_with('//') {
|
||||
comment_lines << trimmed.trim_string_left('//').trim_space()
|
||||
} else if !in_struct && (trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ')) {
|
||||
} else if !in_struct && (trimmed.starts_with('struct ')
|
||||
|| trimmed.starts_with('pub struct ')) {
|
||||
in_struct = true
|
||||
struct_lines << line
|
||||
|
||||
@@ -138,8 +139,8 @@ pub fn parse_struct(code_ string) !Struct {
|
||||
is_mut_field := current_section.contains('mut')
|
||||
|
||||
fields << StructField{
|
||||
name: field_name
|
||||
typ: field_type
|
||||
name: field_name
|
||||
typ: field_type
|
||||
is_pub: is_pub_field
|
||||
is_mut: is_mut_field
|
||||
}
|
||||
@@ -149,14 +150,13 @@ pub fn parse_struct(code_ string) !Struct {
|
||||
description := comment_lines.join('\n')
|
||||
|
||||
return Struct{
|
||||
name: struct_name
|
||||
name: struct_name
|
||||
description: description
|
||||
is_pub: is_pub
|
||||
fields: fields
|
||||
is_pub: is_pub
|
||||
fields: fields
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct Interface {
|
||||
pub mut:
|
||||
name string
|
||||
|
||||
@@ -266,21 +266,41 @@ pub fn parse_type(type_str string) Type {
|
||||
} else if type_str_trimmed == 'int' {
|
||||
return Integer{}
|
||||
} else if type_str_trimmed == 'u8' {
|
||||
return Integer{bytes: 8, signed: false}
|
||||
return Integer{
|
||||
bytes: 8
|
||||
signed: false
|
||||
}
|
||||
} else if type_str_trimmed == 'u16' {
|
||||
return Integer{bytes: 16, signed: false}
|
||||
return Integer{
|
||||
bytes: 16
|
||||
signed: false
|
||||
}
|
||||
} else if type_str_trimmed == 'u32' {
|
||||
return Integer{bytes: 32, signed: false}
|
||||
return Integer{
|
||||
bytes: 32
|
||||
signed: false
|
||||
}
|
||||
} else if type_str_trimmed == 'u64' {
|
||||
return Integer{bytes: 64, signed: false}
|
||||
return Integer{
|
||||
bytes: 64
|
||||
signed: false
|
||||
}
|
||||
} else if type_str_trimmed == 'i8' {
|
||||
return Integer{bytes: 8}
|
||||
return Integer{
|
||||
bytes: 8
|
||||
}
|
||||
} else if type_str_trimmed == 'i16' {
|
||||
return Integer{bytes: 16}
|
||||
return Integer{
|
||||
bytes: 16
|
||||
}
|
||||
} else if type_str_trimmed == 'i32' {
|
||||
return Integer{bytes: 32}
|
||||
return Integer{
|
||||
bytes: 32
|
||||
}
|
||||
} else if type_str_trimmed == 'i64' {
|
||||
return Integer{bytes: 64}
|
||||
return Integer{
|
||||
bytes: 64
|
||||
}
|
||||
}
|
||||
|
||||
// Check for array types
|
||||
|
||||
@@ -66,15 +66,17 @@ fn find_closing_brace(content string, start_i int) ?int {
|
||||
// RETURNS:
|
||||
// string - the function block including comments, or error if not found
|
||||
pub fn get_function_from_file(file_path string, function_name string) !Function {
|
||||
content := os.read_file(file_path) or { return error('Failed to read file ${file_path}: ${err}') }
|
||||
content := os.read_file(file_path) or {
|
||||
return error('Failed to read file ${file_path}: ${err}')
|
||||
}
|
||||
|
||||
vfile := parse_vfile(content) or { return error('Failed to parse file ${file_path}: ${err}') }
|
||||
|
||||
if fn_obj := vfile.get_function(function_name) {
|
||||
return fn_obj
|
||||
}
|
||||
}
|
||||
|
||||
return error('function ${function_name} not found in file ${file_path}')
|
||||
return error('function ${function_name} not found in file ${file_path}')
|
||||
}
|
||||
|
||||
// get_function_from_module searches for a function in all V files within a module
|
||||
@@ -91,14 +93,10 @@ pub fn get_function_from_module(module_path string, function_name string) !Funct
|
||||
log.error('Found ${v_files} V files in ${module_path}')
|
||||
for v_file in v_files {
|
||||
// Read the file content
|
||||
content := os.read_file(v_file) or {
|
||||
continue
|
||||
}
|
||||
content := os.read_file(v_file) or { continue }
|
||||
|
||||
// Parse the file
|
||||
vfile := parse_vfile(content) or {
|
||||
continue
|
||||
}
|
||||
vfile := parse_vfile(content) or { continue }
|
||||
|
||||
// Look for the function
|
||||
if fn_obj := vfile.get_function(function_name) {
|
||||
@@ -139,7 +137,7 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
|
||||
if i == -1 {
|
||||
type_import := content.split_into_lines().filter(it.contains('import')
|
||||
&& it.contains(type_name))
|
||||
&& it.contains(type_name))
|
||||
if type_import.len > 0 {
|
||||
log.debug('debugzoooo')
|
||||
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
|
||||
|
||||
@@ -241,6 +241,6 @@ pub fn (mut d Decoder) get_map_bytes() !map[string][]u8 {
|
||||
|
||||
// Gets GID from encoded string
|
||||
pub fn (mut d Decoder) get_gid() !gid.GID {
|
||||
gid_str := d.get_string()!
|
||||
return gid.new(gid_str)
|
||||
gid_str := d.get_string()!
|
||||
return gid.new(gid_str)
|
||||
}
|
||||
|
||||
@@ -191,15 +191,15 @@ fn test_map_bytes() {
|
||||
fn test_gid() {
|
||||
// Test with a standard GID
|
||||
mut e := new()
|
||||
mut g1 := gid.new("myproject:123")!
|
||||
mut g1 := gid.new('myproject:123')!
|
||||
e.add_gid(g1)
|
||||
|
||||
// Test with a GID that has a default circle name
|
||||
mut g2 := gid.new_from_parts("", 999)!
|
||||
mut g2 := gid.new_from_parts('', 999)!
|
||||
e.add_gid(g2)
|
||||
|
||||
// Test with a GID that has spaces before fixing
|
||||
mut g3 := gid.new("project1:456")!
|
||||
mut g3 := gid.new('project1:456')!
|
||||
e.add_gid(g3)
|
||||
|
||||
mut d := decoder_new(e.data)
|
||||
@@ -211,47 +211,47 @@ fn test_gid() {
|
||||
fn test_currency() {
|
||||
// Create USD currency manually
|
||||
mut usd_curr := currency.Currency{
|
||||
name: 'USD'
|
||||
name: 'USD'
|
||||
usdval: 1.0
|
||||
}
|
||||
|
||||
// Create EUR currency manually
|
||||
mut eur_curr := currency.Currency{
|
||||
name: 'EUR'
|
||||
name: 'EUR'
|
||||
usdval: 1.1
|
||||
}
|
||||
|
||||
// Create Bitcoin currency manually
|
||||
mut btc_curr := currency.Currency{
|
||||
name: 'BTC'
|
||||
name: 'BTC'
|
||||
usdval: 60000.0
|
||||
}
|
||||
|
||||
// Create TFT currency manually
|
||||
mut tft_curr := currency.Currency{
|
||||
name: 'TFT'
|
||||
name: 'TFT'
|
||||
usdval: 0.05
|
||||
}
|
||||
|
||||
// Create currency amounts
|
||||
mut usd_amount := currency.Amount{
|
||||
currency: usd_curr
|
||||
val: 1.5
|
||||
val: 1.5
|
||||
}
|
||||
|
||||
mut eur_amount := currency.Amount{
|
||||
currency: eur_curr
|
||||
val: 100.0
|
||||
val: 100.0
|
||||
}
|
||||
|
||||
mut btc_amount := currency.Amount{
|
||||
currency: btc_curr
|
||||
val: 0.01
|
||||
val: 0.01
|
||||
}
|
||||
|
||||
mut tft_amount := currency.Amount{
|
||||
currency: tft_curr
|
||||
val: 1000.0
|
||||
val: 1000.0
|
||||
}
|
||||
|
||||
mut e := new()
|
||||
|
||||
@@ -31,23 +31,23 @@ pub fn new(txt_ string) !GID {
|
||||
}
|
||||
|
||||
cid_str := parts[1].trim_space()
|
||||
cid := cid_str.u32() //TODO: what if this is no nr?
|
||||
cid := cid_str.u32() // TODO: what if this is no nr?
|
||||
|
||||
return GID{
|
||||
circle: circle
|
||||
cid: cid
|
||||
cid: cid
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_parts(circle_ string, cid u32) !GID {
|
||||
mut circle:=circle_
|
||||
mut circle := circle_
|
||||
if circle.trim_space() == '' {
|
||||
circle="default"
|
||||
circle = 'default'
|
||||
}
|
||||
|
||||
return GID{
|
||||
circle: circle
|
||||
cid: cid
|
||||
cid: cid
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
// Define a struct for test cases
|
||||
struct PrefixEdgeCaseTest {
|
||||
prefix string
|
||||
prefix string
|
||||
expected_keys []string
|
||||
}
|
||||
|
||||
@@ -17,10 +17,20 @@ fn test_edge_case_prefix_search() {
|
||||
|
||||
// Keys with a common prefix that may cause issues
|
||||
keys := [
|
||||
'test', 'testing', 'tea', 'team', 'technology',
|
||||
'apple', 'application', 'appreciate',
|
||||
'banana', 'bandage', 'band',
|
||||
'car', 'carpet', 'carriage'
|
||||
'test',
|
||||
'testing',
|
||||
'tea',
|
||||
'team',
|
||||
'technology',
|
||||
'apple',
|
||||
'application',
|
||||
'appreciate',
|
||||
'banana',
|
||||
'bandage',
|
||||
'band',
|
||||
'car',
|
||||
'carpet',
|
||||
'carriage',
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
@@ -36,33 +46,33 @@ fn test_edge_case_prefix_search() {
|
||||
test_cases := [
|
||||
// prefix, expected_keys
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'te'
|
||||
prefix: 'te'
|
||||
expected_keys: ['test', 'testing', 'tea', 'team', 'technology']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'tes'
|
||||
prefix: 'tes'
|
||||
expected_keys: ['test', 'testing']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'tea'
|
||||
prefix: 'tea'
|
||||
expected_keys: ['tea', 'team']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'a'
|
||||
prefix: 'a'
|
||||
expected_keys: ['apple', 'application', 'appreciate']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'ba'
|
||||
prefix: 'ba'
|
||||
expected_keys: ['banana', 'bandage', 'band']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'ban'
|
||||
prefix: 'ban'
|
||||
expected_keys: ['banana', 'band']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'c'
|
||||
prefix: 'c'
|
||||
expected_keys: ['car', 'carpet', 'carriage']
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
for test_case in test_cases {
|
||||
@@ -75,8 +85,7 @@ fn test_edge_case_prefix_search() {
|
||||
}
|
||||
|
||||
// Check count matches
|
||||
assert result.len == expected_keys.len,
|
||||
'For prefix "${prefix}": expected ${expected_keys.len} keys, got ${result.len} (keys: ${result})'
|
||||
assert result.len == expected_keys.len, 'For prefix "${prefix}": expected ${expected_keys.len} keys, got ${result.len} (keys: ${result})'
|
||||
|
||||
// Check all expected keys are present
|
||||
for key in expected_keys {
|
||||
@@ -102,8 +111,13 @@ fn test_tricky_insertion_order() {
|
||||
// Insert keys in a specific order that might trigger the issue
|
||||
// Insert 'team' first, then 'test', etc. to ensure tree layout is challenging
|
||||
tricky_keys := [
|
||||
'team', 'test', 'technology', 'tea', // 'te' prefix cases
|
||||
'car', 'carriage', 'carpet' // 'ca' prefix cases
|
||||
'team',
|
||||
'test',
|
||||
'technology',
|
||||
'tea', // 'te' prefix cases
|
||||
'car',
|
||||
'carriage',
|
||||
'carpet', // 'ca' prefix cases
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
// Define a struct for test cases
|
||||
struct PrefixTestCase {
|
||||
prefix string
|
||||
prefix string
|
||||
expected_count int
|
||||
}
|
||||
|
||||
@@ -17,13 +17,31 @@ fn test_complex_prefix_search() {
|
||||
|
||||
// Insert a larger set of keys with various prefixes
|
||||
keys := [
|
||||
'a', 'ab', 'abc', 'abcd', 'abcde',
|
||||
'b', 'bc', 'bcd', 'bcde',
|
||||
'c', 'cd', 'cde',
|
||||
'x', 'xy', 'xyz',
|
||||
'test', 'testing', 'tested', 'tests',
|
||||
'team', 'teammate', 'teams',
|
||||
'tech', 'technology', 'technical'
|
||||
'a',
|
||||
'ab',
|
||||
'abc',
|
||||
'abcd',
|
||||
'abcde',
|
||||
'b',
|
||||
'bc',
|
||||
'bcd',
|
||||
'bcde',
|
||||
'c',
|
||||
'cd',
|
||||
'cde',
|
||||
'x',
|
||||
'xy',
|
||||
'xyz',
|
||||
'test',
|
||||
'testing',
|
||||
'tested',
|
||||
'tests',
|
||||
'team',
|
||||
'teammate',
|
||||
'teams',
|
||||
'tech',
|
||||
'technology',
|
||||
'technical',
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
@@ -54,8 +72,8 @@ fn test_complex_prefix_search() {
|
||||
PrefixTestCase{'x', 3},
|
||||
PrefixTestCase{'xy', 2},
|
||||
PrefixTestCase{'xyz', 1},
|
||||
PrefixTestCase{'z', 0}, // No matches
|
||||
PrefixTestCase{'', keys.len} // All keys
|
||||
PrefixTestCase{'z', 0}, // No matches
|
||||
PrefixTestCase{'', keys.len}, // All keys
|
||||
]
|
||||
|
||||
for test_case in test_cases {
|
||||
@@ -87,13 +105,21 @@ fn test_special_prefix_search() {
|
||||
|
||||
// Insert keys with special characters and longer strings
|
||||
special_keys := [
|
||||
'user:1:profile', 'user:1:settings', 'user:1:posts',
|
||||
'user:2:profile', 'user:2:settings',
|
||||
'config:app:name', 'config:app:version', 'config:app:debug',
|
||||
'config:db:host', 'config:db:port',
|
||||
'data:2023:01:01', 'data:2023:01:02', 'data:2023:02:01',
|
||||
'user:1:profile',
|
||||
'user:1:settings',
|
||||
'user:1:posts',
|
||||
'user:2:profile',
|
||||
'user:2:settings',
|
||||
'config:app:name',
|
||||
'config:app:version',
|
||||
'config:app:debug',
|
||||
'config:db:host',
|
||||
'config:db:port',
|
||||
'data:2023:01:01',
|
||||
'data:2023:01:02',
|
||||
'data:2023:02:01',
|
||||
'very:long:key:with:multiple:segments:and:special:characters:!@#$%^&*()',
|
||||
'another:very:long:key:with:different:segments'
|
||||
'another:very:long:key:with:different:segments',
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
@@ -118,7 +144,7 @@ fn test_special_prefix_search() {
|
||||
PrefixTestCase{'data:2023:01:', 2},
|
||||
PrefixTestCase{'very:', 1},
|
||||
PrefixTestCase{'another:', 1},
|
||||
PrefixTestCase{'nonexistent:', 0}
|
||||
PrefixTestCase{'nonexistent:', 0},
|
||||
]
|
||||
|
||||
for test_case in special_test_cases {
|
||||
@@ -153,7 +179,7 @@ fn test_prefix_search_performance() {
|
||||
mut large_keys := []string{}
|
||||
|
||||
for prefix in prefixes {
|
||||
for i in 0..100 {
|
||||
for i in 0 .. 100 {
|
||||
large_keys << '${prefix}:${i}:name'
|
||||
}
|
||||
}
|
||||
@@ -184,7 +210,7 @@ fn test_prefix_search_performance() {
|
||||
|
||||
// Test more specific prefixes
|
||||
for prefix in prefixes {
|
||||
for i in 0..10 {
|
||||
for i in 0 .. 10 {
|
||||
specific_prefix := '${prefix}:${i}'
|
||||
result := tree.list(specific_prefix) or {
|
||||
assert false, 'Failed to list keys with prefix "${specific_prefix}": ${err}'
|
||||
|
||||
@@ -62,11 +62,11 @@ fn deserialize_node(data []u8) !Node {
|
||||
right_id := d.get_u32()!
|
||||
|
||||
return Node{
|
||||
character: character
|
||||
character: character
|
||||
is_end_of_string: is_end_of_string
|
||||
value: value
|
||||
left_id: left_id
|
||||
middle_id: middle_id
|
||||
right_id: right_id
|
||||
value: value
|
||||
left_id: left_id
|
||||
middle_id: middle_id
|
||||
right_id: right_id
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ module tst
|
||||
fn test_node_serialization() {
|
||||
// Create a leaf node (end of string)
|
||||
leaf_node := Node{
|
||||
character: `a`
|
||||
character: `a`
|
||||
is_end_of_string: true
|
||||
value: 'test value'.bytes()
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
value: 'test value'.bytes()
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
|
||||
// Serialize the leaf node
|
||||
@@ -30,12 +30,12 @@ fn test_node_serialization() {
|
||||
|
||||
// Create an internal node (not end of string)
|
||||
internal_node := Node{
|
||||
character: `b`
|
||||
character: `b`
|
||||
is_end_of_string: false
|
||||
value: []u8{}
|
||||
left_id: 10
|
||||
middle_id: 20
|
||||
right_id: 30
|
||||
value: []u8{}
|
||||
left_id: 10
|
||||
middle_id: 20
|
||||
right_id: 30
|
||||
}
|
||||
|
||||
// Serialize the internal node
|
||||
@@ -56,12 +56,12 @@ fn test_node_serialization() {
|
||||
|
||||
// Create a root node
|
||||
root_node := Node{
|
||||
character: 0 // null character for root
|
||||
character: 0 // null character for root
|
||||
is_end_of_string: false
|
||||
value: []u8{}
|
||||
left_id: 5
|
||||
middle_id: 15
|
||||
right_id: 25
|
||||
value: []u8{}
|
||||
left_id: 5
|
||||
middle_id: 15
|
||||
right_id: 25
|
||||
}
|
||||
|
||||
// Serialize the root node
|
||||
@@ -85,12 +85,12 @@ fn test_node_serialization() {
|
||||
fn test_special_serialization() {
|
||||
// Create a node with special character
|
||||
special_node := Node{
|
||||
character: `!` // special character
|
||||
character: `!` // special character
|
||||
is_end_of_string: true
|
||||
value: 'special value with spaces and symbols: !@#$%^&*()'.bytes()
|
||||
left_id: 42
|
||||
middle_id: 99
|
||||
right_id: 123
|
||||
value: 'special value with spaces and symbols: !@#$%^&*()'.bytes()
|
||||
left_id: 42
|
||||
middle_id: 99
|
||||
right_id: 123
|
||||
}
|
||||
|
||||
// Serialize the special node
|
||||
@@ -111,17 +111,17 @@ fn test_special_serialization() {
|
||||
|
||||
// Create a node with a large value
|
||||
mut large_value := []u8{len: 1000}
|
||||
for i in 0..1000 {
|
||||
for i in 0 .. 1000 {
|
||||
large_value[i] = u8(i % 256)
|
||||
}
|
||||
|
||||
large_node := Node{
|
||||
character: `z`
|
||||
character: `z`
|
||||
is_end_of_string: true
|
||||
value: large_value
|
||||
left_id: 1
|
||||
middle_id: 2
|
||||
right_id: 3
|
||||
value: large_value
|
||||
left_id: 1
|
||||
middle_id: 2
|
||||
right_id: 3
|
||||
}
|
||||
|
||||
// Serialize the large node
|
||||
@@ -138,7 +138,7 @@ fn test_special_serialization() {
|
||||
assert deserialized_large.value.len == large_node.value.len, 'Value length mismatch'
|
||||
|
||||
// Check each byte of the large value
|
||||
for i in 0..large_node.value.len {
|
||||
for i in 0 .. large_node.value.len {
|
||||
assert deserialized_large.value[i] == large_node.value[i], 'Value byte mismatch at index ${i}'
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@ fn test_special_serialization() {
|
||||
fn test_version_handling() {
|
||||
// Create a valid node
|
||||
valid_node := Node{
|
||||
character: `a`
|
||||
character: `a`
|
||||
is_end_of_string: true
|
||||
value: 'test'.bytes()
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
value: 'test'.bytes()
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
|
||||
// Serialize the node
|
||||
|
||||
@@ -5,12 +5,12 @@ import freeflowuniverse.herolib.data.ourdb
|
||||
// Represents a node in the ternary search tree
|
||||
struct Node {
|
||||
mut:
|
||||
character u8 // The character stored at this nodexs
|
||||
is_end_of_string bool // Flag indicating if this node represents the end of a key
|
||||
value []u8 // The value associated with the key (if this node is the end of a key)
|
||||
left_id u32 // Database ID for left child (character < node.character)
|
||||
middle_id u32 // Database ID for middle child (character == node.character)
|
||||
right_id u32 // Database ID for right child (character > node.character)
|
||||
character u8 // The character stored at this nodexs
|
||||
is_end_of_string bool // Flag indicating if this node represents the end of a key
|
||||
value []u8 // The value associated with the key (if this node is the end of a key)
|
||||
left_id u32 // Database ID for left child (character < node.character)
|
||||
middle_id u32 // Database ID for middle child (character == node.character)
|
||||
right_id u32 // Database ID for right child (character > node.character)
|
||||
}
|
||||
|
||||
// TST represents a ternary search tree data structure
|
||||
@@ -45,12 +45,12 @@ pub fn new(args NewArgs) !TST {
|
||||
// We'll use a null character (0) for the root node
|
||||
println('Creating new root node')
|
||||
root := Node{
|
||||
character: 0
|
||||
character: 0
|
||||
is_end_of_string: false
|
||||
value: []u8{}
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
value: []u8{}
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
root_id = db.set(data: serialize_node(root))!
|
||||
println('Root node created with ID: ${root_id}')
|
||||
@@ -83,12 +83,12 @@ pub fn (mut self TST) set(key string, value []u8) ! {
|
||||
if self.root_id == 0 {
|
||||
println('Tree is empty, creating root node')
|
||||
root := Node{
|
||||
character: 0
|
||||
character: 0
|
||||
is_end_of_string: false
|
||||
value: []u8{}
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
value: []u8{}
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
self.root_id = self.db.set(data: serialize_node(root))!
|
||||
println('Root node created with ID: ${self.root_id}')
|
||||
@@ -129,12 +129,12 @@ fn (mut self TST) insert_recursive(node_id u32, key string, pos int, value []u8)
|
||||
|
||||
// Create a node for this character
|
||||
new_node := Node{
|
||||
character: key[pos]
|
||||
character: key[pos]
|
||||
is_end_of_string: pos == key.len - 1
|
||||
value: if pos == key.len - 1 { value.clone() } else { []u8{} }
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
value: if pos == key.len - 1 { value.clone() } else { []u8{} }
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
new_id := self.db.set(data: serialize_node(new_node))!
|
||||
println('New node created with ID: ${new_id}, character: ${key[pos]} (${key[pos].ascii_str()}), is_end: ${pos == key.len - 1}')
|
||||
@@ -189,7 +189,7 @@ fn (mut self TST) insert_recursive(node_id u32, key string, pos int, value []u8)
|
||||
node.value = value
|
||||
self.db.set(id: node_id, data: serialize_node(node))!
|
||||
} else {
|
||||
println('Going middle for next character: ${key[pos+1]} (${key[pos+1].ascii_str()})')
|
||||
println('Going middle for next character: ${key[pos + 1]} (${key[pos + 1].ascii_str()})')
|
||||
// Move to the next character in the key
|
||||
node.middle_id = self.insert_recursive(node.middle_id, key, pos + 1, value)!
|
||||
self.db.set(id: node_id, data: serialize_node(node))!
|
||||
@@ -246,18 +246,14 @@ fn (mut self TST) search_recursive(node_id u32, key string, pos int) ![]u8 {
|
||||
// Left branch
|
||||
if key[pos] < node.character {
|
||||
println('Going left')
|
||||
result = self.search_recursive(node.left_id, key, pos) or {
|
||||
return error(err.str())
|
||||
}
|
||||
result = self.search_recursive(node.left_id, key, pos) or { return error(err.str()) }
|
||||
return result
|
||||
}
|
||||
|
||||
// Right branch
|
||||
if key[pos] > node.character {
|
||||
println('Going right')
|
||||
result = self.search_recursive(node.right_id, key, pos) or {
|
||||
return error(err.str())
|
||||
}
|
||||
result = self.search_recursive(node.right_id, key, pos) or { return error(err.str()) }
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -286,9 +282,7 @@ fn (mut self TST) search_recursive(node_id u32, key string, pos int) ![]u8 {
|
||||
}
|
||||
|
||||
println('Going to middle child')
|
||||
result = self.search_recursive(node.middle_id, key, pos + 1) or {
|
||||
return error(err.str())
|
||||
}
|
||||
result = self.search_recursive(node.middle_id, key, pos + 1) or { return error(err.str()) }
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -405,7 +399,7 @@ fn (mut self TST) delete_recursive(node_id u32, key string, pos int) !bool {
|
||||
}
|
||||
} else {
|
||||
// Move to the next character in the key
|
||||
println('Moving to next character: ${key[pos+1]} (${key[pos+1].ascii_str()})')
|
||||
println('Moving to next character: ${key[pos + 1]} (${key[pos + 1].ascii_str()})')
|
||||
if node.middle_id == 0 {
|
||||
println('Middle child is null, key not found')
|
||||
return error('Key not found')
|
||||
|
||||
@@ -45,19 +45,27 @@ fn (mut self TST) navigate_to_prefix(node_id u32, prefix string, pos int) Prefix
|
||||
// Base case: no node or out of bounds
|
||||
if node_id == 0 || pos >= prefix.len {
|
||||
return PrefixSearchResult{
|
||||
found: false
|
||||
found: false
|
||||
node_id: 0
|
||||
prefix: ''
|
||||
prefix: ''
|
||||
}
|
||||
}
|
||||
|
||||
// Get node
|
||||
node_data := self.db.get(node_id) or {
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
return PrefixSearchResult{
|
||||
found: false
|
||||
node_id: 0
|
||||
prefix: ''
|
||||
}
|
||||
}
|
||||
|
||||
node := deserialize_node(node_data) or {
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
return PrefixSearchResult{
|
||||
found: false
|
||||
node_id: 0
|
||||
prefix: ''
|
||||
}
|
||||
}
|
||||
|
||||
println('Navigating node ${node_id}: char=${node.character} (${node.character.ascii_str()}), pos=${pos}, prefix_char=${prefix[pos]} (${prefix[pos].ascii_str()})')
|
||||
@@ -80,16 +88,20 @@ fn (mut self TST) navigate_to_prefix(node_id u32, prefix string, pos int) Prefix
|
||||
println('Reached end of prefix at node ${node_id}')
|
||||
// Return the exact prefix string that was passed in
|
||||
return PrefixSearchResult{
|
||||
found: true
|
||||
found: true
|
||||
node_id: node_id
|
||||
prefix: prefix
|
||||
prefix: prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Not at end of prefix, check middle child
|
||||
if node.middle_id == 0 {
|
||||
println('No middle child, prefix not found')
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
return PrefixSearchResult{
|
||||
found: false
|
||||
node_id: 0
|
||||
prefix: ''
|
||||
}
|
||||
}
|
||||
|
||||
// Continue to middle child with next character
|
||||
@@ -112,7 +124,7 @@ fn (mut self TST) collect_keys_with_prefix(node_id u32, prefix string, mut resul
|
||||
// If this node is an end of string and it's not the root, we found a key
|
||||
if node.is_end_of_string && node.character != 0 {
|
||||
// The prefix may already contain this node's character
|
||||
if prefix.len == 0 || prefix[prefix.len-1] != node.character {
|
||||
if prefix.len == 0 || prefix[prefix.len - 1] != node.character {
|
||||
println('Found complete key: "${prefix}${node.character.ascii_str()}"')
|
||||
result << prefix + node.character.ascii_str()
|
||||
} else {
|
||||
@@ -131,7 +143,7 @@ fn (mut self TST) collect_keys_with_prefix(node_id u32, prefix string, mut resul
|
||||
mut next_prefix := prefix
|
||||
if node.character != 0 { // Skip root node
|
||||
// Only add the character if it's not already at the end of the prefix
|
||||
if prefix.len == 0 || prefix[prefix.len-1] != node.character {
|
||||
if prefix.len == 0 || prefix[prefix.len - 1] != node.character {
|
||||
next_prefix += node.character.ascii_str()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +116,8 @@ fn (p CustomProperty) xml_str() string {
|
||||
fn test_custom_property() {
|
||||
// Test custom property
|
||||
custom_prop := CustomProperty{
|
||||
name: 'author'
|
||||
value: 'Kristof'
|
||||
name: 'author'
|
||||
value: 'Kristof'
|
||||
namespace: 'C'
|
||||
}
|
||||
|
||||
@@ -138,9 +138,8 @@ fn test_propfind_response() {
|
||||
<D:href>/test-file.txt</D:href>
|
||||
${props.xml_str()}
|
||||
</D:response>
|
||||
</D:multistatus>'
|
||||
</D:multistatus>' // Verify the XML structure
|
||||
|
||||
// Verify the XML structure
|
||||
assert xml_output.contains('<D:multistatus')
|
||||
assert xml_output.contains('<D:response>')
|
||||
assert xml_output.contains('<D:href>')
|
||||
@@ -183,8 +182,8 @@ fn test_supported_lock_detailed() {
|
||||
fn test_proppatch_request() {
|
||||
// Create property to set
|
||||
author_prop := CustomProperty{
|
||||
name: 'author'
|
||||
value: 'Kristof'
|
||||
name: 'author'
|
||||
value: 'Kristof'
|
||||
namespace: 'C'
|
||||
}
|
||||
|
||||
@@ -195,9 +194,8 @@ fn test_proppatch_request() {
|
||||
${author_prop.xml_str()}
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</D:propertyupdate>'
|
||||
</D:propertyupdate>' // Check structure
|
||||
|
||||
// Check structure
|
||||
assert proppatch_set.contains('<D:propertyupdate')
|
||||
assert proppatch_set.contains('<D:set>')
|
||||
assert proppatch_set.contains('<D:prop>')
|
||||
|
||||
@@ -604,20 +604,13 @@ fn is_binary_content_type(content_type string) bool {
|
||||
normalized := content_type.to_lower()
|
||||
|
||||
// Check for common binary file types
|
||||
return normalized.contains('application/octet-stream') ||
|
||||
(normalized.contains('application/') && (
|
||||
normalized.contains('msword') ||
|
||||
normalized.contains('excel') ||
|
||||
normalized.contains('powerpoint') ||
|
||||
normalized.contains('pdf') ||
|
||||
normalized.contains('zip') ||
|
||||
normalized.contains('gzip') ||
|
||||
normalized.contains('x-tar') ||
|
||||
normalized.contains('x-7z') ||
|
||||
normalized.contains('x-rar')
|
||||
)) ||
|
||||
(normalized.contains('image/') && !normalized.contains('svg')) ||
|
||||
normalized.contains('audio/') ||
|
||||
normalized.contains('video/') ||
|
||||
normalized.contains('vnd.openxmlformats') // Office documents
|
||||
return normalized.contains('application/octet-stream')
|
||||
|| (normalized.contains('application/') && (normalized.contains('msword')
|
||||
|| normalized.contains('excel') || normalized.contains('powerpoint')
|
||||
|| normalized.contains('pdf') || normalized.contains('zip')
|
||||
|| normalized.contains('gzip') || normalized.contains('x-tar')
|
||||
|| normalized.contains('x-7z') || normalized.contains('x-rar')))
|
||||
|| (normalized.contains('image/') && !normalized.contains('svg'))
|
||||
|| normalized.contains('audio/') || normalized.contains('video/')
|
||||
|| normalized.contains('vnd.openxmlformats') // Office documents
|
||||
}
|
||||
|
||||
@@ -66,19 +66,35 @@ fn (mut server Server) get_entry_property(entry &vfs.FSEntry, name string) !Prop
|
||||
property_name := if name.contains(':') { name.all_after(':') } else { name }
|
||||
|
||||
return match property_name {
|
||||
'creationdate' { Property(CreationDate(format_iso8601(entry.get_metadata().created_time()))) }
|
||||
'getetag' { Property(GetETag(entry.get_metadata().id.str())) }
|
||||
'resourcetype' { Property(ResourceType(entry.is_dir())) }
|
||||
'creationdate' {
|
||||
Property(CreationDate(format_iso8601(entry.get_metadata().created_time())))
|
||||
}
|
||||
'getetag' {
|
||||
Property(GetETag(entry.get_metadata().id.str()))
|
||||
}
|
||||
'resourcetype' {
|
||||
Property(ResourceType(entry.is_dir()))
|
||||
}
|
||||
'getlastmodified', 'lastmodified_server' {
|
||||
// Both standard getlastmodified and custom lastmodified_server properties
|
||||
// return the same information
|
||||
Property(GetLastModified(texttools.format_rfc1123(entry.get_metadata().modified_time())))
|
||||
}
|
||||
'getcontentlength' { Property(GetContentLength(entry.get_metadata().size.str())) }
|
||||
'quota-available-bytes' { Property(QuotaAvailableBytes(16184098816)) }
|
||||
'quota-used-bytes' { Property(QuotaUsedBytes(16184098816)) }
|
||||
'quotaused' { Property(QuotaUsed(16184098816)) }
|
||||
'quota' { Property(Quota(16184098816)) }
|
||||
'getcontentlength' {
|
||||
Property(GetContentLength(entry.get_metadata().size.str()))
|
||||
}
|
||||
'quota-available-bytes' {
|
||||
Property(QuotaAvailableBytes(16184098816))
|
||||
}
|
||||
'quota-used-bytes' {
|
||||
Property(QuotaUsedBytes(16184098816))
|
||||
}
|
||||
'quotaused' {
|
||||
Property(QuotaUsed(16184098816))
|
||||
}
|
||||
'quota' {
|
||||
Property(Quota(16184098816))
|
||||
}
|
||||
'displayname' {
|
||||
// RFC 4918, Section 15.2: displayname is a human-readable name for UI display
|
||||
// For now, we use the filename as the displayname, but this could be enhanced
|
||||
@@ -127,16 +143,24 @@ fn (mut server Server) get_responses(entry vfs.FSEntry, req PropfindRequest, pat
|
||||
}
|
||||
// main entry response
|
||||
responses << PropfindResponse{
|
||||
href: ensure_leading_slash(if entry.is_dir() { '${path.trim_string_right('/')}/' } else { path })
|
||||
href: ensure_leading_slash(if entry.is_dir() {
|
||||
'${path.trim_string_right('/')}/'
|
||||
} else {
|
||||
path
|
||||
})
|
||||
// not_found: entry.get_unfound_properties(req)
|
||||
found_props: properties
|
||||
}
|
||||
} else {
|
||||
responses << PropfindResponse{
|
||||
href: ensure_leading_slash(if entry.is_dir() { '${path.trim_string_right('/')}/' } else { path })
|
||||
// not_found: entry.get_unfound_properties(req)
|
||||
found_props: server.get_properties(entry)
|
||||
}
|
||||
responses << PropfindResponse{
|
||||
href: ensure_leading_slash(if entry.is_dir() {
|
||||
'${path.trim_string_right('/')}/'
|
||||
} else {
|
||||
path
|
||||
})
|
||||
// not_found: entry.get_unfound_properties(req)
|
||||
found_props: server.get_properties(entry)
|
||||
}
|
||||
}
|
||||
|
||||
if !entry.is_dir() || req.depth == .zero {
|
||||
|
||||
@@ -491,7 +491,8 @@ fn test_server_propfind() ! {
|
||||
// Now that we know the correct format, check for it - directories have both leading and trailing slashes
|
||||
assert ctx.res.body.contains('<D:href>/${root_dir}/</D:href>')
|
||||
// Should only include the requested resource
|
||||
assert !ctx.res.body.contains('<D:href>/${file_in_root}</D:href>') && !ctx.res.body.contains('<D:href>/${file_in_root}')
|
||||
assert !ctx.res.body.contains('<D:href>/${file_in_root}</D:href>')
|
||||
&& !ctx.res.body.contains('<D:href>/${file_in_root}')
|
||||
|
||||
// Test PROPFIND with depth=1 (resource and immediate children)
|
||||
mut ctx2 := Context{
|
||||
|
||||
@@ -10,7 +10,7 @@ import freeflowuniverse.herolib.core.redisclient
|
||||
__global (
|
||||
circle_global map[string]&CircleCoordinator
|
||||
circle_default string
|
||||
action_queues map[string]&ActionQueue
|
||||
action_queues map[string]&ActionQueue
|
||||
)
|
||||
|
||||
// HeroRunner is the main factory for managing jobs, agents, services, circles and names
|
||||
@@ -101,7 +101,7 @@ pub fn new(args_ CircleCoordinatorArgs) !&CircleCoordinator {
|
||||
@[params]
|
||||
pub struct ActionQueueArgs {
|
||||
pub mut:
|
||||
name string = 'default' // Name of the queue
|
||||
name string = 'default' // Name of the queue
|
||||
redis_addr string // Redis server address, defaults to 'localhost:6379'
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn new_action_queue(args ActionQueueArgs) !&ActionQueue {
|
||||
|
||||
// Create ActionQueue
|
||||
mut action_queue := &ActionQueue{
|
||||
name: queue_name
|
||||
name: queue_name
|
||||
queue: &redis_queue
|
||||
redis: redis
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ pub enum ActionJobStatus {
|
||||
@[heap]
|
||||
pub struct ActionJob {
|
||||
pub mut:
|
||||
guid string
|
||||
guid string
|
||||
heroscript string
|
||||
created ourtime.OurTime
|
||||
deadline ourtime.OurTime
|
||||
status ActionJobStatus
|
||||
error string // Error message if job failed
|
||||
async bool // Whether the job should be processed asynchronously
|
||||
circleid string // ID of the circle this job belongs to
|
||||
created ourtime.OurTime
|
||||
deadline ourtime.OurTime
|
||||
status ActionJobStatus
|
||||
error string // Error message if job failed
|
||||
async bool // Whether the job should be processed asynchronously
|
||||
circleid string // ID of the circle this job belongs to
|
||||
}
|
||||
|
||||
// ActionQueue is a queue of actions to be processed, which comes from a redis queue
|
||||
@@ -46,13 +46,13 @@ pub fn new_action_job(heroscript string) ActionJob {
|
||||
deadline.warp('+1h') or { panic('Failed to set deadline: ${err}') }
|
||||
|
||||
return ActionJob{
|
||||
guid: time.now().unix_milli().str(),
|
||||
heroscript: heroscript,
|
||||
created: now,
|
||||
deadline: deadline,
|
||||
status: .pending,
|
||||
async: false,
|
||||
circleid: ''
|
||||
guid: time.now().unix_milli().str()
|
||||
heroscript: heroscript
|
||||
created: now
|
||||
deadline: deadline
|
||||
status: .pending
|
||||
async: false
|
||||
circleid: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,12 +120,12 @@ pub fn (mut q ActionQueue) get_job(guid string) !ActionJob {
|
||||
|
||||
// Retrieve job fields
|
||||
mut job := ActionJob{
|
||||
guid: guid,
|
||||
heroscript: q.redis.hget(job_key, 'heroscript')!,
|
||||
status: ActionJobStatus.pending, // Default value, will be overwritten
|
||||
error: '', // Default empty error message
|
||||
async: false, // Default to synchronous
|
||||
circleid: '' // Default to empty circle ID
|
||||
guid: guid
|
||||
heroscript: q.redis.hget(job_key, 'heroscript')!
|
||||
status: ActionJobStatus.pending // Default value, will be overwritten
|
||||
error: '' // Default empty error message
|
||||
async: false // Default to synchronous
|
||||
circleid: '' // Default to empty circle ID
|
||||
}
|
||||
|
||||
// Parse created time
|
||||
|
||||
@@ -40,6 +40,7 @@ pub fn (mut m DBHandler[T]) get_data(id u32) ![]u8 {
|
||||
}
|
||||
return item_data
|
||||
}
|
||||
|
||||
pub fn (mut m DBHandler[T]) exists(id u32) !bool {
|
||||
item_data := m.session_state.dbs.db_data_core.get(id) or { return false }
|
||||
return item_data != []u8{}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.core { DBHandler, SessionState, new_dbhandler }
|
||||
import freeflowuniverse.herolib.hero.db.models.circle { User, Role }
|
||||
import freeflowuniverse.herolib.hero.db.models.circle { Role, User }
|
||||
|
||||
type UserObj = User
|
||||
|
||||
@[heap]
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
|
||||
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
import freeflowuniverse.herolib.hero.db.models.circle { User, Role }
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.models.circle { Role, User }
|
||||
|
||||
// dumps serializes a User struct to binary data
|
||||
pub fn (user UserObj) dumps() ![]u8 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.core { SessionState, new_session }
|
||||
import freeflowuniverse.herolib.hero.db.core { new_session }
|
||||
import freeflowuniverse.herolib.hero.db.models.circle { Role }
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import os
|
||||
@@ -111,4 +111,3 @@ pub fn test_user_db() ! {
|
||||
|
||||
println('User DB Test completed successfully')
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import freeflowuniverse.herolib.data.ourtime
|
||||
// our attempt to make a message object which can be used for email as well as chat
|
||||
pub struct Base {
|
||||
pub mut:
|
||||
id u32
|
||||
creation_time ourtime.OurTime
|
||||
mod_time ourtime.OurTime // Last modified time
|
||||
comments []u32
|
||||
id u32
|
||||
creation_time ourtime.OurTime
|
||||
mod_time ourtime.OurTime // Last modified time
|
||||
comments []u32
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.models.base
|
||||
|
||||
// Define the RecordType enum
|
||||
pub enum RecordType {
|
||||
a
|
||||
aaa
|
||||
cname
|
||||
mx
|
||||
ns
|
||||
ptr
|
||||
soa
|
||||
srv
|
||||
txt
|
||||
a
|
||||
aaa
|
||||
cname
|
||||
mx
|
||||
ns
|
||||
ptr
|
||||
soa
|
||||
srv
|
||||
txt
|
||||
}
|
||||
|
||||
// Define the DomainNamespace struct, represents a full domain with all its records
|
||||
pub struct DomainNameSpace {
|
||||
base.Base
|
||||
base.Base
|
||||
pub mut:
|
||||
id u32
|
||||
domain string
|
||||
description string
|
||||
records []Record
|
||||
admins []u32 // IDs of the admins they need to exist as user in the circle
|
||||
id u32
|
||||
domain string
|
||||
description string
|
||||
records []Record
|
||||
admins []u32 // IDs of the admins they need to exist as user in the circle
|
||||
}
|
||||
|
||||
// Define the Record struct
|
||||
pub struct Record {
|
||||
pub mut:
|
||||
name string
|
||||
text string
|
||||
category RecordType
|
||||
addr []string
|
||||
name string
|
||||
text string
|
||||
category RecordType
|
||||
addr []string
|
||||
}
|
||||
|
||||
pub fn (self DomainNameSpace) index_keys() map[string]string {
|
||||
@@ -42,6 +43,6 @@ pub fn (self DomainNameSpace) index_keys() map[string]string {
|
||||
|
||||
pub fn (self DomainNameSpace) ftindex_keys() map[string]string {
|
||||
return {
|
||||
'description': self.description,
|
||||
'description': self.description
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.models.base
|
||||
|
||||
//there is one group called "everyone" which is the default group for all members and their roles
|
||||
// there is one group called "everyone" which is the default group for all members and their roles
|
||||
pub struct Group {
|
||||
base.Base
|
||||
pub mut:
|
||||
name string // name of the group in a circle, the one "everyone" is the default group
|
||||
description string // optional description
|
||||
members []u32 // pointers to the members of this group
|
||||
name string // name of the group in a circle, the one "everyone" is the default group
|
||||
description string // optional description
|
||||
members []u32 // pointers to the members of this group
|
||||
}
|
||||
|
||||
pub fn (self Group) index_keys() map[string]string {
|
||||
@@ -18,7 +19,7 @@ pub fn (self Group) index_keys() map[string]string {
|
||||
|
||||
pub fn (self Group) ftindex_keys() map[string]string {
|
||||
return {
|
||||
'description': self.description,
|
||||
'members': self.members.map(it.str()).join(",")
|
||||
'description': self.description
|
||||
'members': self.members.map(it.str()).join(',')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ pub enum Role {
|
||||
member
|
||||
contributor
|
||||
guest
|
||||
external //means no right in this circle appart from we register this user
|
||||
external // means no right in this circle appart from we register this user
|
||||
}
|
||||
|
||||
// Member represents a member of a circle
|
||||
pub struct User {
|
||||
base.Base
|
||||
pub mut:
|
||||
name string // name of the member as used in this circle
|
||||
description string // optional description which is relevant to this circle
|
||||
role Role // role of the member in the circle
|
||||
contact_ids []u32 // IDs of contacts linked to this member
|
||||
wallet_ids []u32 // IDs of wallets owned by this member which are relevant to this circle
|
||||
pubkey string // public key of the member as used in this circle
|
||||
name string // name of the member as used in this circle
|
||||
description string // optional description which is relevant to this circle
|
||||
role Role // role of the member in the circle
|
||||
contact_ids []u32 // IDs of contacts linked to this member
|
||||
wallet_ids []u32 // IDs of wallets owned by this member which are relevant to this circle
|
||||
pubkey string // public key of the member as used in this circle
|
||||
}
|
||||
|
||||
pub fn (self User) index_keys() map[string]string {
|
||||
@@ -33,6 +33,6 @@ pub fn (self User) index_keys() map[string]string {
|
||||
|
||||
pub fn (self User) ftindex_keys() map[string]string {
|
||||
return {
|
||||
'description': self.description,
|
||||
'description': self.description
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
module finance
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.models.base
|
||||
|
||||
pub struct Account {
|
||||
base.Base
|
||||
base.Base
|
||||
pub mut:
|
||||
name string //internal name of the account for the user
|
||||
user_id u32 //user id of the owner of the account
|
||||
description string //optional description of the account
|
||||
ledger string //describes the ledger/blockchain where the account is located e.g. "ethereum", "bitcoin" or other institutions
|
||||
address string //address of the account on the blockchain
|
||||
pubkey string
|
||||
assets []Asset
|
||||
name string // internal name of the account for the user
|
||||
user_id u32 // user id of the owner of the account
|
||||
description string // optional description of the account
|
||||
ledger string // describes the ledger/blockchain where the account is located e.g. "ethereum", "bitcoin" or other institutions
|
||||
address string // address of the account on the blockchain
|
||||
pubkey string
|
||||
assets []Asset
|
||||
}
|
||||
|
||||
|
||||
pub fn (self Account) index_keys() map[string]string {
|
||||
return {
|
||||
'name': self.name
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
module finance
|
||||
|
||||
import freeflowuniverse.herolib.hero.db.models.base
|
||||
|
||||
|
||||
pub enum AssetType {
|
||||
erc20
|
||||
erc721
|
||||
erc1155
|
||||
native
|
||||
|
||||
erc20
|
||||
erc721
|
||||
erc1155
|
||||
native
|
||||
}
|
||||
|
||||
pub struct Asset {
|
||||
base.Base
|
||||
base.Base
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
amount f64
|
||||
address string //address of the asset on the blockchain or bank
|
||||
asset_type AssetType //type of the asset
|
||||
decimals u8 //number of decimals of the asset
|
||||
name string
|
||||
description string
|
||||
amount f64
|
||||
address string // address of the asset on the blockchain or bank
|
||||
asset_type AssetType // type of the asset
|
||||
decimals u8 // number of decimals of the asset
|
||||
}
|
||||
|
||||
|
||||
pub fn (self Asset) index_keys() map[string]string {
|
||||
return {
|
||||
'name': self.name
|
||||
|
||||
@@ -11,17 +11,16 @@ pub mut:
|
||||
description string // Event details
|
||||
location string // Event location
|
||||
start_time ourtime.OurTime
|
||||
end_time ourtime.OurTime // End time
|
||||
all_day bool // True if it's an all-day event
|
||||
recurrence string // RFC 5545 Recurrence Rule (e.g., "FREQ=DAILY;COUNT=10")
|
||||
attendees []u32 // List of contact id's
|
||||
organizer u32 // The user (see circle) who created the event
|
||||
status string // "CONFIRMED", "CANCELLED", "TENTATIVE" //TODO: make enum
|
||||
color string // User-friendly color categorization, e.g., "red", "blue" //TODO: make enum
|
||||
end_time ourtime.OurTime // End time
|
||||
all_day bool // True if it's an all-day event
|
||||
recurrence string // RFC 5545 Recurrence Rule (e.g., "FREQ=DAILY;COUNT=10")
|
||||
attendees []u32 // List of contact id's
|
||||
organizer u32 // The user (see circle) who created the event
|
||||
status string // "CONFIRMED", "CANCELLED", "TENTATIVE" //TODO: make enum
|
||||
color string // User-friendly color categorization, e.g., "red", "blue" //TODO: make enum
|
||||
reminder []ourtime.OurTime // Reminder time before the event
|
||||
}
|
||||
|
||||
|
||||
pub fn (self Asset) index_keys() map[string]string {
|
||||
return {
|
||||
'name': self.name
|
||||
|
||||
@@ -6,24 +6,22 @@ import freeflowuniverse.herolib.hero.db.models.base
|
||||
pub struct Contact {
|
||||
base.Base
|
||||
pub mut:
|
||||
name string //name of the contact as we use in this circle
|
||||
first_name string
|
||||
last_name string
|
||||
email []string
|
||||
tel []string
|
||||
name string // name of the contact as we use in this circle
|
||||
first_name string
|
||||
last_name string
|
||||
email []string
|
||||
tel []string
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn (self Contact) index_keys() map[string]string {
|
||||
return map[string]string{} //TODO: name
|
||||
return map[string]string{} // TODO: name
|
||||
}
|
||||
|
||||
pub fn (self Contact) ftindex_keys() map[string]string {
|
||||
return {
|
||||
'first_name': self.first_name
|
||||
'last_name': self.last_name
|
||||
'email': self.email.join(', ')
|
||||
'tel': self.tel.join(', ')
|
||||
'last_name': self.last_name
|
||||
'email': self.email.join(', ')
|
||||
'tel': self.tel.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ pub struct Message {
|
||||
base.Base // Base struct for common fields
|
||||
pub mut:
|
||||
// Database ID
|
||||
id u32 // Database ID (assigned by DBHandler)
|
||||
id u32 // Database ID (assigned by DBHandler)
|
||||
message_id string // Unique identifier for the email
|
||||
folder string // The folder this email belongs to (inbox, sent, drafts, etc.)
|
||||
message string // The email body content
|
||||
attachments []Attachment // Any file attachments
|
||||
send_time ourtime.OurTime
|
||||
|
||||
date i64 // Unix timestamp when the email was sent/received
|
||||
size u32 // Size of the message in bytes
|
||||
read bool // Whether the email has been read
|
||||
flagged bool // Whether the email has been flagged/starred
|
||||
date i64 // Unix timestamp when the email was sent/received
|
||||
size u32 // Size of the message in bytes
|
||||
read bool // Whether the email has been read
|
||||
flagged bool // Whether the email has been flagged/starred
|
||||
|
||||
// Header information
|
||||
subject string
|
||||
@@ -36,15 +36,13 @@ pub struct Attachment {
|
||||
pub mut:
|
||||
filename string
|
||||
content_type string
|
||||
hash string // Hash of the attachment data
|
||||
hash string // Hash of the attachment data
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn (self Message) index_keys() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
pub fn (self Message) ftindex_keys() map[string]string {
|
||||
return map[string]string{} //TODO: add subject and from to this and to and message
|
||||
return map[string]string{} // TODO: add subject and from to this and to and message
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ pub enum BusinessType {
|
||||
// Company represents a company registered in the Freezone
|
||||
pub struct Company {
|
||||
pub mut:
|
||||
id u32
|
||||
name string
|
||||
id u32
|
||||
name string
|
||||
registration_number string
|
||||
incorporation_date ourtime.OurTime
|
||||
fiscal_year_end string
|
||||
@@ -68,9 +68,9 @@ pub fn (company Company) dumps() ![]u8 {
|
||||
enc.add_u16(u16(company.shareholders.len))
|
||||
for shareholder in company.shareholders {
|
||||
// Encode each shareholder's fields
|
||||
enc.add_u32(shareholder.id)
|
||||
enc.add_u32(shareholder.company_id)
|
||||
enc.add_u32(shareholder.user_id)
|
||||
enc.add_u32(shareholder.id)
|
||||
enc.add_u32(shareholder.company_id)
|
||||
enc.add_u32(shareholder.user_id)
|
||||
enc.add_string(shareholder.name)
|
||||
enc.add_string(shareholder.shares.str()) // Store shares as string to preserve precision
|
||||
enc.add_string(shareholder.percentage.str()) // Store as string to preserve precision
|
||||
|
||||
@@ -46,16 +46,15 @@ pub mut:
|
||||
// Attendee represents an attendee of a board meeting
|
||||
pub struct Attendee {
|
||||
pub mut:
|
||||
id u32
|
||||
meeting_id u32
|
||||
user_id u32
|
||||
name string
|
||||
role AttendeeRole
|
||||
status AttendeeStatus
|
||||
created_at ourtime.OurTime
|
||||
id u32
|
||||
meeting_id u32
|
||||
user_id u32
|
||||
name string
|
||||
role AttendeeRole
|
||||
status AttendeeStatus
|
||||
created_at ourtime.OurTime
|
||||
}
|
||||
|
||||
|
||||
// dumps serializes the Meeting to a byte array
|
||||
pub fn (meeting Meeting) dumps() ![]u8 {
|
||||
mut enc := encoder.new()
|
||||
|
||||
@@ -2,7 +2,6 @@ module models
|
||||
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
import freeflowuniverse.herolib.data.currency
|
||||
import freeflowuniverse.herolib.core.texttools { name_fix }
|
||||
|
||||
@@ -32,19 +31,19 @@ pub mut:
|
||||
// Product represents a product or service offered by the Freezone
|
||||
pub struct Product {
|
||||
pub mut:
|
||||
id u32
|
||||
name string
|
||||
description string
|
||||
price currency.Currency
|
||||
type_ ProductType
|
||||
category string
|
||||
status ProductStatus
|
||||
created_at ourtime.OurTime
|
||||
updated_at ourtime.OurTime
|
||||
max_amount u16 // means allows us to define how many max of this there are
|
||||
id u32
|
||||
name string
|
||||
description string
|
||||
price currency.Currency
|
||||
type_ ProductType
|
||||
category string
|
||||
status ProductStatus
|
||||
created_at ourtime.OurTime
|
||||
updated_at ourtime.OurTime
|
||||
max_amount u16 // means allows us to define how many max of this there are
|
||||
purchase_till ourtime.OurTime
|
||||
active_till ourtime.OurTime // after this product no longer active if e.g. a service
|
||||
components []ProductComponent
|
||||
active_till ourtime.OurTime // after this product no longer active if e.g. a service
|
||||
components []ProductComponent
|
||||
}
|
||||
|
||||
// dumps serializes the Product to a byte array
|
||||
@@ -104,7 +103,9 @@ pub fn product_loads(data []u8) !Product {
|
||||
|
||||
// Decode Currency from bytes
|
||||
price_bytes := d.get_bytes()!
|
||||
currency_bytes := currency.CurrencyBytes{data: price_bytes}
|
||||
currency_bytes := currency.CurrencyBytes{
|
||||
data: price_bytes
|
||||
}
|
||||
product.price = currency.from_bytes(currency_bytes)!
|
||||
|
||||
product.type_ = unsafe { ProductType(d.get_u8()!) }
|
||||
|
||||
@@ -2,7 +2,6 @@ module models
|
||||
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
import freeflowuniverse.herolib.data.currency
|
||||
|
||||
// SaleStatus represents the status of a sale
|
||||
@@ -39,7 +38,6 @@ pub mut:
|
||||
active_till ourtime.OurTime // after this product no longer active if e.g. a service
|
||||
}
|
||||
|
||||
|
||||
// dumps serializes the Sale to a byte array
|
||||
pub fn (sale Sale) dumps() ![]u8 {
|
||||
mut enc := encoder.new()
|
||||
@@ -103,7 +101,9 @@ pub fn sale_loads(data []u8) !Sale {
|
||||
|
||||
// Decode Currency from bytes
|
||||
total_amount_bytes := d.get_bytes()!
|
||||
currency_bytes := currency.CurrencyBytes{data: total_amount_bytes}
|
||||
currency_bytes := currency.CurrencyBytes{
|
||||
data: total_amount_bytes
|
||||
}
|
||||
sale.total_amount = currency.from_bytes(currency_bytes)!
|
||||
|
||||
sale.status = unsafe { SaleStatus(d.get_u8()!) }
|
||||
@@ -130,11 +130,15 @@ pub fn sale_loads(data []u8) !Sale {
|
||||
|
||||
// Decode Currency from bytes
|
||||
unit_price_bytes := d.get_bytes()!
|
||||
unit_price_currency_bytes := currency.CurrencyBytes{data: unit_price_bytes}
|
||||
unit_price_currency_bytes := currency.CurrencyBytes{
|
||||
data: unit_price_bytes
|
||||
}
|
||||
item.unit_price = currency.from_bytes(unit_price_currency_bytes)!
|
||||
|
||||
subtotal_bytes := d.get_bytes()!
|
||||
subtotal_currency_bytes := currency.CurrencyBytes{data: subtotal_bytes}
|
||||
subtotal_currency_bytes := currency.CurrencyBytes{
|
||||
data: subtotal_bytes
|
||||
}
|
||||
item.subtotal = currency.from_bytes(subtotal_currency_bytes)!
|
||||
|
||||
active_till_str := d.get_string()!
|
||||
|
||||
@@ -68,7 +68,7 @@ pub fn shareholder_loads(data []u8) !Shareholder {
|
||||
percentage_str := d.get_string()!
|
||||
shareholder.percentage = percentage_str.f64()
|
||||
|
||||
shareholder.type_ = unsafe { ShareholderType(d.get_u8()!) }
|
||||
shareholder.type_ = unsafe { ShareholderType(d.get_u8()!) }
|
||||
|
||||
since_str := d.get_string()!
|
||||
shareholder.since = ourtime.new(since_str)!
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user