Builtin Library
- ..
- README.md6.6 KB
- about.yaml482 B
- behavior.luau10.2 KB
- inspector.luau1.7 KB
- template
- type.yaml2.9 KB
README.md
tool (asset type)
A tool is one named operation: a single luau typed function inside a toolboxe's <name>.tool asset,
callable as <toolbox>.<tool>. The function's signature is the tool's interface,
so anyone can call it correctly without reading its code.
A tool is also a shell command, zero <toolbox> <tool> <args>; guides core/tools,
"A tool's command line", covers its flags, paths, render.luau and exit status.
Each tool has a declared input shape and return shape; the engine wraps each call's result in an envelope.
Why use a tool?
Tools are typed operations with a known serialized output format, made to enable efficient operation in the engine and provide an easily callable interface for operations around a topic.
They are also useful in that they can be called from a multitude of surfaces, from execute, to bash to tool_exec
to users Tools portion in the edui.
Compared to say a module.assetType, that might contain multiple functions with varying shapes, experct references or specific formats of tables as input and could return potentially anyting including flat tables.
that has multiple callable functionm in that they might return anything a tool is meant to be called with a serialized value, and return a serialized value.
A tool is meant to perform an self contained operation and return results/info/relevant data in a readable format, including eg. image data.
Whenever you identify an operation that requires specific orchestration and or is something that could be a repeated operation and makes it easier to manage the project/task or when you want to expose logic written in luau
to the other call surfaces like bash under its zero command, it propably should be a <name>.tool asset.
How tools are written
In its simplicity, a tool is a single Typed luau function that returns a value. written plainly, in its basic form it could look like this.
-- probe.toolbox/onCall.tool
--
-- Fire when a named function runs, seeing its arguments and what it returned.
--- A toolbox offered shared module, that in this case,contains a type shared by the tools in their toolbox.
local shared = require(".shared")
local M = {}
export type OnCallResult = {
ok: boolean,
id: string?,
result: string?,
error: string?,
}
--!desc example tool
--!arg path target path for the operation
--!arg opts `{ notice, when?, run?, capture?, label?, limit?, cooldown?, freeze?, silent? }`.
-- `capture` is Luau, compiled against the hit's context the way `run`
-- is, that returns a picture for the hit — a capture tool you aim:
-- `return tools.use("capture", "fromEntity", { entity = entity })`. It
-- runs once the hit is recorded, and the hit's notice then carries the
-- picture under `shot`, delivered as a picture.
--!return `{ ok, id, result }`.
--!example "arg1", arg2
typed function M.gatherResults(path: string, opts: shared.ArmOpts): OnCallResult
local modulePath, key = splitPath(path)
--- the tool code implementation
local results = myCodeResults
--- the tool call returns a table with keys, that is then wrapped in a return envelope.
return { ok = true, id = results.id, result = results.gathered }
end
return M
Now when called, the tool runs and returns the envelope with the inspectable results.
Why return plainly, and fail by raising
Every caller gets a tool's outcome in the same form, success or failure, so
return the value itself and raise an error when the tool can't do its job. A
tool that returns its own { ok = false } reads as a success that happened to
return that table. Make the error say what to do instead: the caller reads it
and acts on it.
What the shell prints
zero <toolbox> <tool> prints the text the tool's render.luau states: a
module in the tool's folder returning one function (result) -> string | { out, err?, exit? }. A returned string is the output, and { out, err?, exit? }
sets the output, the standard error text and the exit status. A call that raises
exits 1, and a command line the shell cannot bind to the tool's parameters exits 2.
When to answer later instead of blocking
A caller waits for the tool to return, and might be calling multiple tools at the same time.
Should your call run past the waiting threshold in order to return, even if it yields
it comes back as a handle to a background task that will then complete on its own,
but require the caller to separately read the task results.
For a call that is small, answering inline with the result is better. When a
tool can tell that this particular call will be long (a big bake, a large
batch, a slow fetch), it hands itself to the background with
task.background() early, so the caller gets the handle at once and carries on
with other work.
When to post a notice
The return value is the answer to what was asked. A notice is for what the
caller needs to know besides that: a side effect it didn't ask for, a return to one that has no caller,
notices should be treated as a way to make sure your tool can return quickly, when you arent sure how
long it takes for the results to be ready. It allows the agent to continue working, and then react when
the notice fires. notices.post delivers it to the caller,
and an agent that is idle is woken by it, so the caller learns it without
polling. Name in it what to do about it. A notice for something the return
value already says is noise.
Returning more than text
Tools also support returning images, that the agent can then directly read from the tool response, regardles of what surface they used to call the tool. Images also work on notices, so you can eg attach visual results to the eventual outcomes of your tool results and the agent will see them the second the notice fires.
What a tool may do that other code may not
A tool, and code only tools call, may act on whatever an input names, resolving
an asset by a name that only arrives with the call. That is what makes a tool
the place for operations that depend on what they are given. Tools are for
working on a world rather than running in the game: a component may not call
one unless it declares declare { componentSafe = true }, and a tool doesn't
call another tool, so logic several tools need goes in the toolbox's shared
module or exist elsewhere.
For information how to group tools and organize them, and how they are called read toolbox.assetType readme.
In the Inspector
Tool: how the tool is called (its signature), what it does, each argument, what it returns, and its examples, with Open for its source.