Skip to content

createBuiltinTools

JavaScript · package toolnexus · SPEC §4A · js/src/builtin.ts

function createBuiltinTools(): Tool[]

Builds all ten built-in tools — opencode’s file/shell/web tool set, ported with identical names and input schemas — with no config, no filtering, no toolkit. Every call returns a fresh Tool[] in a fixed order: bash, read, write, edit, grep, glob, webfetch, question, apply_patch, todowrite.

  • You want the built-in toolset outside a Toolkit — feeding it straight to your own loop, or to a provider adapter, with nothing else mixed in.
  • You are building your own filtering on top and selectBuiltins’s name-map shape doesn’t fit — createBuiltinTools() then .filter(...) is the escape hatch.
  • You want to inspect the full set (names, descriptions, schemas) without going through createToolkit’s builtins option at all.

Every failure mode inside a built-in tool — a missing file, a bad regex, a non-zero exit code — is reported as ToolResult{isError: true}, never a thrown exception. That contract holds whether you reach the tool through createBuiltinTools directly or through a toolkit; nothing about wrapping changes error behavior.

1. The smallest useful call — the fixed set, in order

Section titled “1. The smallest useful call — the fixed set, in order”
import assert from "node:assert"
import { createBuiltinTools } from "toolnexus"
const tools = createBuiltinTools()
assert.equal(tools.length, 10)
assert.deepEqual(
tools.map((t) => t.name),
["bash", "read", "write", "edit", "grep", "glob", "webfetch", "question", "apply_patch", "todowrite"],
)
assert.ok(tools.every((t) => t.source === "builtin"))
console.log("ok:", tools.length, "built-ins:", tools.map((t) => t.name).join(", "))

2. Real file I/O — write, read, edit round-trip

Section titled “2. Real file I/O — write, read, edit round-trip”

The built-ins are not stubs — write/read/edit touch the real filesystem, scoped here to a throwaway temp directory.

import assert from "node:assert"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { createBuiltinTools } from "toolnexus"
const tools = createBuiltinTools()
const byName = Object.fromEntries(tools.map((t) => [t.name, t]))
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "toolnexus-builtins-"))
const file = path.join(dir, "notes.txt")
try {
const w = await byName.write.execute({ path: file, content: "alpha\nbeta\n" })
assert.equal(w.isError, false)
assert.equal(fs.readFileSync(file, "utf8"), "alpha\nbeta\n")
const r = await byName.read.execute({ path: file, offset: 2, limit: 1 })
assert.equal(r.output, "beta")
const e = await byName.edit.execute({ path: file, oldString: "beta", newString: "gamma" })
assert.equal(e.isError, false)
assert.equal(fs.readFileSync(file, "utf8"), "alpha\ngamma\n")
// A file that doesn't exist is a ToolResult error, never a thrown exception.
const miss = await byName.read.execute({ path: path.join(dir, "nope.txt") })
assert.equal(miss.isError, true)
console.log("ok:", w.output.trim(), "|", r.output, "->", fs.readFileSync(file, "utf8").trim())
} finally {
fs.rmSync(dir, { recursive: true, force: true })
}

3. The full surface — bash, grep/glob, and the question tool’s suspension

Section titled “3. The full surface — bash, grep/glob, and the question tool’s suspension”

bash runs a real shell command; grep/glob search a real directory tree; question doesn’t answer at all on its first call — it returns a §10 suspension (metadata.pending), then, given the resolved Answer back via ctx, returns the answer verbatim.

import assert from "node:assert"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { createBuiltinTools } from "toolnexus"
const tools = createBuiltinTools()
const byName = Object.fromEntries(tools.map((t) => [t.name, t]))
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "toolnexus-builtins-full-"))
try {
fs.writeFileSync(path.join(dir, "a.txt"), "needle here\nnothing\n")
fs.writeFileSync(path.join(dir, "b.md"), "needle in markdown\n")
const bash = await byName.bash.execute({ command: "printf hello-bash" })
assert.equal(bash.output, "hello-bash")
const grep = await byName.grep.execute({ pattern: "needle", path: dir })
assert.equal(grep.metadata?.count, 2)
const glob = await byName.glob.execute({ pattern: "*.txt", path: dir })
assert.equal(glob.output, "a.txt")
// First call: no answer yet ⇒ suspends with a §10 Request under metadata.pending.
const asked = await byName.question.execute({ questions: [{ question: "Which file first?" }] })
assert.equal(asked.metadata?.pending?.kind, "question")
const req = asked.metadata!.pending as { id: string }
// Re-executed with the resolved Answer in ctx ⇒ the answer is returned, not re-asked.
const answered = await byName.question.execute(
{ questions: [{ question: "Which file first?" }] },
{ answer: { id: req.id, ok: true, data: { answers: ["a.txt"] } } },
)
assert.deepEqual(JSON.parse(answered.output), { answers: ["a.txt"] })
console.log("ok:", bash.output, "|", grep.metadata?.count, "matches |", answered.output)
} finally {
fs.rmSync(dir, { recursive: true, force: true })
}

Fixed order, each source: "builtin":

# Name What it does
1 bash Run a shell command; combined stdout+stderr; non-zero exit is an error.
2 read Read a UTF-8 file, optionally windowed by offset/limit.
3 write Create/overwrite a file, creating parent directories.
4 edit Exact-string replace; unique-by-default, replaceAll for every occurrence.
5 grep Regex search file contents under a directory.
6 glob List files matching a glob under a directory.
7 webfetch HTTP GET a URL as text/markdown/html.
8 question Ask the host a question; suspends via §10 until answered.
9 apply_patch Apply a Begin/End Patch (Add/Update/Delete File), atomically.
10 todowrite Replace and render the session todo list.
  • selectBuiltins — Pick which built-in file/shell/web tools are in play, by name or by source toggle.