# Provider Loader System # Dynamic provider loading and interface validation use registry.nu * use interface.nu * use ../utils/logging.nu * # Load provider dynamically with validation export def load-provider [name: string]: nothing -> record { # Silent loading - only log errors, not info/success # Provider loading happens multiple times due to wrapper scripts, logging creates noise # Check if provider is available if not (is-provider-available $name) { log-error $"Provider ($name) not found or not available" "provider-loader" return {} } # Get provider registry entry let provider_entry = (get-provider-entry $name) # Load the provider module let provider_instance = if ($provider_entry.type == "core") { load-core-provider $provider_entry } else { load-extension-provider $provider_entry } if not ($provider_instance | is-empty) { # Validate interface compliance let validation = (validate-provider-interface $name $provider_instance) if $validation.valid { $provider_instance } else { log-error $"Provider ($name) failed interface validation" "provider-loader" log-error $"Missing functions: ($validation.missing_functions | str join ', ')" "provider-loader" {} } } else { log-error $"Failed to load provider module for ($name)" "provider-loader" {} } } # Load core provider def load-core-provider [provider_entry: record]: nothing -> record { # For core providers, use direct module loading # Core providers should be in the core library path let module_path = $provider_entry.entry_point # Create provider instance record { name: $provider_entry.name type: "core" loaded: true entry_point: $module_path load_time: (date now) } } # Load extension provider def load-extension-provider [provider_entry: record]: nothing -> record { # For extension providers, use the adapter pattern let module_path = $provider_entry.entry_point # Test that the provider file exists and has the required functions let test_cmd = $"nu -c \"use ($module_path) *; get-provider-metadata | to json\"" let test_result_check = (do { nu -c $test_cmd | complete }) if ($test_result_check.exit_code != 0) { log-error $"Provider validation failed for ($provider_entry.name)" "provider-loader" {} } else { # Create provider instance record { name: $provider_entry.name type: "extension" loaded: true entry_point: $module_path load_time: (date now) metadata: ($test_result_check.stdout | from json) } } } # Get provider instance (with caching) export def get-provider [name: string]: nothing -> record { # Check if already loaded in this session let cache_key = $"PROVIDER_LOADED_($name)" if ($env | get -o $cache_key) != null { return ($env | get $cache_key) } # Load and cache the provider let provider = (load-provider $name) if not ($provider | is-empty) { load-env { $cache_key: $provider } } $provider } # Call a provider function dynamically export def call-provider-function [ provider_name: string function_name: string ...args ]: nothing -> any { # Get provider entry let provider_entry = (get-provider-entry $provider_name) if ($provider_entry | is-empty) { log-error $"Provider ($provider_name) not found" "provider-loader" return null } # Use direct import and call via a wrapper script let temp_dir = ($env.TMPDIR? | default "/tmp") # Save arguments as a list to a single file let args_file = ($temp_dir | path join $"provider_args_(random chars).nuon") $args | to nuon | save --force $args_file # Create wrapper script that loads args and calls function # Build individual arg references based on count let wrapper_script = ($temp_dir | path join $"provider_wrapper_(random chars).nu") let arg_count = ($args | length) let call_line = if $arg_count == 0 { $"($function_name) | to json" } else if $arg_count == 1 { $"($function_name) \(\$args | get 0\) | to json" } else if $arg_count == 2 { $"($function_name) \(\$args | get 0\) \(\$args | get 1\) | to json" } else if $arg_count == 3 { $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) | to json" } else if $arg_count == 4 { $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) | to json" } else if $arg_count == 5 { $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) \(\$args | get 4\) | to json" } else { log-error $"Too many arguments \(($arg_count)\) for provider function" "provider-loader" return null } let script_content = $" use ($provider_entry.entry_point) * let args = \(open ($args_file)\) ($call_line) " $script_content | save --force $wrapper_script # Execute the wrapper script let result = (do { nu $wrapper_script } | complete) # Clean up temp files if ($args_file | path exists) { rm -f $args_file } if ($wrapper_script | path exists) { rm -f $wrapper_script } # Return result if successful, null otherwise if $result.exit_code == 0 { # Try to parse as structured data (JSON, NUON, etc), fallback to string let output = ($result.stdout | str trim) if ($output | is-empty) { null } else if $output == "true" { true } else if $output == "false" { false } else if ($output | str starts-with "{") or ($output | str starts-with "[") { # Try JSON parse - use error handling for Nushell 0.107 let parsed = (do -i { $output | from json }) if ($parsed | is-empty) { $output } else { $parsed } } else { $output } } else { log-error $"Provider function call failed: ($result.stderr)" "provider-loader" null } } # Get required provider functions def get-required-functions []: nothing -> list { [ "get-provider-metadata" "query_servers" "server_exists" "check_server_requirements" ] } # Validate provider interface compliance def validate-provider-interface [provider_name: string, provider_instance: record]: nothing -> record { let required_functions = (get-required-functions) mut missing_functions = [] mut valid = true # Check if provider file exists let provider_file = $provider_instance.entry_point if not ($provider_file | path exists) { return { valid: false missing_functions: ["provider file not found"] provider: $provider_name } } # Check each required function for func in $required_functions { let check_cmd = $"nu -c \"use ($provider_file) *; help commands | where name == '($func)' | length\"" let check_result = (nu -c $check_cmd | complete) if $check_result.exit_code == 0 { let func_count = ($check_result.stdout | str trim | into int) if $func_count == 0 { $missing_functions = ($missing_functions | append $func) $valid = false } } else { $missing_functions = ($missing_functions | append $func) $valid = false } } { valid: $valid missing_functions: $missing_functions provider: $provider_name checked_functions: ($required_functions | length) validation_time: (date now) } } # Load multiple providers export def load-providers [provider_names: list]: nothing -> record { mut results = { successful: 0 failed: 0 total: ($provider_names | length) details: [] } for provider_name in $provider_names { let result = (load-provider $provider_name) if not ($result | is-empty) { $results.successful = ($results.successful + 1) $results.details = ($results.details | append { provider: $provider_name status: "success" loaded: true }) } else { $results.failed = ($results.failed + 1) $results.details = ($results.details | append { provider: $provider_name status: "failed" loaded: false }) } } $results } # Check provider health export def check-provider-health [provider_name: string]: nothing -> record { let health_check = { provider: $provider_name available: false loadable: false interface_valid: false metadata_accessible: false timestamp: (date now) } # Check if provider is available let available = (is-provider-available $provider_name) let updated_health = ($health_check | merge { available: $available }) if not $available { return $updated_health } # Try to load provider let provider_instance = (load-provider $provider_name) let loadable = not ($provider_instance | is-empty) let updated_health = ($updated_health | merge { loadable: $loadable }) if not $loadable { return $updated_health } # Check interface validation let validation = (validate-provider-interface $provider_name $provider_instance) let updated_health = ($updated_health | merge { interface_valid: $validation.valid }) # Check metadata access let provider_entry = (get-provider-entry $provider_name) let metadata_cmd = $"nu -c \"use ($provider_entry.entry_point) *; get-provider-metadata\"" let metadata_result = (nu -c $metadata_cmd | complete) let metadata_accessible = ($metadata_result.exit_code == 0) $updated_health | merge { metadata_accessible: $metadata_accessible } } # Check health of all providers export def check-all-providers-health []: nothing -> table { let providers = (list-providers --available-only) $providers | each {|provider| check-provider-health $provider.name } } # Get loader statistics export def get-loader-stats []: nothing -> record { let provider_stats = (get-provider-stats) let health_checks = (check-all-providers-health) { total_providers: $provider_stats.total_providers available_providers: $provider_stats.available_providers healthy_providers: ($health_checks | where interface_valid == true | length) last_check: (date now) } }