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.
When to use it
Section titled “When to use it”- 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’sbuiltinsoption at all.
Why this and not the alternative
Section titled “Why this and not the alternative”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.
Examples
Section titled “Examples”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 })}What you get back
Section titled “What you get back”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. |
See also
Section titled “See also”selectBuiltins— Pick which built-in file/shell/web tools are in play, by name or by source toggle.