Skip to content

BuiltinTools.create

Java · package io.github.muthuishere:toolnexus · SPEC §4A · BuiltinTools.java

public static List<Tool> create()

Builds all ten built-in tools — bash, read, write, edit, grep, glob, webfetch, question, apply_patch, todowrite, in that fixed order — with no filtering and no config. Each is source "builtin", with the same name and inputSchema as opencode’s built-ins, and each obeys the uniform contract: a failure is ToolResult{isError:true}, never a thrown exception across the tool boundary.

When you want the raw, complete built-in set — every tool, unconditionally — to inspect, filter yourself with custom logic, or register directly without going through a Toolkit or a config object at all.

Toolkit.create with no builtins option set is effectively create() merged with every other source — the ten built-ins ship on by default.

import io.github.muthuishere.toolnexus.*;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<Tool> tools = BuiltinTools.create();
List<String> expected = List.of(
"bash", "read", "write", "edit", "grep", "glob",
"webfetch", "question", "apply_patch", "todowrite"
);
List<String> names = tools.stream().map(Tool::name).toList();
if (!names.equals(expected)) throw new AssertionError(names);
for (Tool t : tools) {
if (!t.source().equals("builtin")) throw new AssertionError(t.name() + " source");
}
System.out.println("ok: " + names);
}
}

2. Calling read directly — a failure is a ToolResult, not a thrown exception

Section titled “2. Calling read directly — a failure is a ToolResult, not a thrown exception”
import io.github.muthuishere.toolnexus.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public class Example {
static Tool find(List<Tool> tools, String name) {
for (Tool t : tools) if (t.name().equals(name)) return t;
throw new IllegalStateException("no such builtin: " + name);
}
public static void main(String[] args) throws Exception {
Path tmp = Files.createTempFile("toolnexus-docs", ".txt");
Files.writeString(tmp, "line one\nline two\nline three\n");
Tool read = find(BuiltinTools.create(), "read");
ToolResult ok = read.execute(Map.of("path", tmp.toString()), new ToolContext());
if (ok.isError() || !ok.output().contains("line two")) throw new AssertionError(ok.output());
// A missing file is a tool ERROR, not an exception.
ToolResult missing = read.execute(Map.of("path", tmp.toString() + ".does-not-exist"), new ToolContext());
if (!missing.isError()) throw new AssertionError("expected isError");
System.out.println("ok: read " + ok.output().lines().count() + " lines, missing-file handled");
}
}

3. Registering the raw set into a Toolkit, without going through builtins(cfg)

Section titled “3. Registering the raw set into a Toolkit, without going through builtins(cfg)”
import io.github.muthuishere.toolnexus.*;
public class Example {
public static void main(String[] args) {
try (Toolkit tk = Toolkit.create(new Toolkit.Options())) {
// builtins(...) was never set on Options — but create() gives the same ten tools
// directly, useful when you want to register a hand-picked subset instead.
for (Tool t : BuiltinTools.create()) {
if (tk.get(t.name()) == null) tk.register(t);
}
if (tk.get("bash") == null) throw new AssertionError("bash missing");
if (!tk.get("bash").source().equals("builtin")) throw new AssertionError("source");
System.out.println("ok: " + tk.toOpenAI().size() + " tools registered");
}
}
}
Member Type What it is
create() List<Tool> All ten built-ins, fixed order, source() == "builtin" on each.
Failure contract Every built-in returns ToolResult{isError:true} on failure — never throws across execute.
Paths Resolve relative to the process working directory unless absolute.