module toolRegistry export loadTools, registerTool, getTools, clearTools using Dates using JSON using ..type # Global registry — populated at runtime by loadTools() or registerTool() const _registry = Vector{agentTool}() # Auto-register the built-in listTools tool function __init__() registerTool(_listTool()) end """ List tool definition — lets the agent query available tools for collision detection when creating new tools via writeTool. """ function _listTool()::agentTool return agentTool( name = "listTools", label = "List Tools", description = "List all available tools with their names, labels, and descriptions. Use this before creating a new tool to check for name collisions.", inputSchema = Dict{String,Any}( "type" => "object", "properties" => Dict{String,Any}(), "required" => Any[] ), execute = (toolCallId, args, signal, onPartialResult) -> begin tools = getTools() if isempty(tools) result_text = "No tools registered." else lines = String["- $(t.name): $(t.label) — $(t.description)" for t in tools] result_text = "Available tools:\n" * join(lines, "\n") end return agentToolResult( [textContent(result_text)], Dict{Any,Any}("count" => length(tools)), nothing, false ) end, prepareArguments = nothing, validateRequiredArgs = nothing, parallelToolExecute = false ) end """ Load all tool modules from a directory. Scans `dir` for `.jl` files. Each file must define a function named `getTool()::agentTool`. Files are sorted alphabetically so tool registration order is deterministic. # Tool file format Each `.jl` file defines one function `getTool()` that returns an `agentTool`: ```julia # src/tools/getWeather.jl function getTool()::agentTool return agentTool( name = "getWeather", label = "Weather Lookup", description = "Fetch current weather and forecast for a given city.", inputSchema = Dict{String,Any}( "type" => "object", "properties" => Dict( "city" => Dict("type" => "string", "description" => "City and country"), "units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"], "default" => "celsius") ), "required" => ["city"] ), execute = (toolCallId, args, signal, onPartialResult) -> begin city = args["city"] return agentToolResult( [textContent("Sunny, 22C in Bangkok")], Dict{Any,Any}(), nothing, false ) end, prepareArguments = nothing, parallelToolExecute = false ) end ``` # Arguments - `dir::String`: Directory path to scan for `.jl` tool files # Returns - `Vector{agentTool}`: All loaded tools # Errors - Throws `ArgumentError` if a tool file does not define a `getTool` function """ function loadTools(dir::String)::Vector{agentTool} if !isdir(dir) throw(ArgumentError("Tool directory does not exist: $dir")) end tools = agentTool[] jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir)) sort!(jl_files) for filename in jl_files filepath = joinpath(dir, filename) println("[toolRegistry] Loading tool from: $filepath") # Include the file in the current module scope so all types resolve # (agentTool, textContent, agentToolResult, etc. are all available) include(filepath) # Validate that getTool was defined (include() places it in current module scope) if !isdefined(@__MODULE__, :getTool) throw(ArgumentError( "Tool file $(filepath) does not define a `getTool()` function. " * "Each tool file must define: function getTool()::agentTool ... end" )) end # Call getTool() — it runs in current scope where types are visible # Use invokelatest to handle world-age semantics after include() tool = invokelatest(getTool) if !(tool isa agentTool) throw(ArgumentError( "getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))" )) end push!(_registry, tool) push!(tools, tool) println("[toolRegistry] Loaded tool: $(tool.name) — $(tool.label)") end return tools end """ Register a single agentTool into the global registry. # Arguments - `tool::agentTool`: The tool to register # Returns - `Vector{agentTool}`: Updated registry """ function registerTool(tool::agentTool)::Vector{agentTool} push!(_registry, tool) println("[toolRegistry] Registered tool: $(tool.name)") return _registry end """ Get all registered tools. # Returns - `Vector{agentTool}`: Copy of the registry """ function getTools()::Vector{agentTool} return deepcopy(_registry) end """ Clear all registered tools from the global registry. """ function clearTools()::Nothing empty!(_registry) println("[toolRegistry] Registry cleared") return nothing end end # module