Builtin Tool Authoring Guide

SkillAI & models

Helps you build tool packages that let an AI agent call custom tools with manifests, executors, and runtimes.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Builtin Tool Authoring Guide skill

About this capability

Build LobeHub builtin tool packages. Use when adding agent-callable tools, manifests, executors, runtimes, inspectors, renders, placeholders, streaming, interventions, portals, or tool registries.

What this skill tells your AI

The instructions your AI receives, as published by lobehub/lobehub in .agents/skills/builtin-tool/SKILL.md and read by ahel’s review.

A builtin tool is a package the agent runtime can call. It ships five faces:

FaceLives inAudience
Manifest + typessrc/{manifest,types,systemRole}.tsThe LLM (tool spec + system prompt)
ExecutionRuntimesrc/ExecutionRuntime/Server / desktop / any runtime caller
Executorsrc/client/executor/Frontend (wraps stores/services)
Client UIsrc/client/{Inspector,Render,…}/Chat UI
Registry wiringpackages/builtin-tools/src/*.ts + src/store/tool/slices/builtin/executors/index.tsFramework

Read These First

QuestionDoc
Where do files live? What does each face do? Wiring?architecture.md
How do I name the tool, design APIs, write the manifest, executor, ExecutionRuntime?tool-design.md
How do I build Inspector / Render / Placeholder / Streaming / Intervention / Portal?ui/

When to Use This Skill

  • Creating a new packages/builtin-tool-<name>/ package
  • Adding a new API method to an existing builtin tool
  • Building or restyling any of the 6 client surfaces for a tool
  • Wiring a tool into the central registries
  • Debugging "tool not found / API not found / render not showing / placeholder stuck" errors

Top-Level Design Principles

  1. lobe-<domain> identifier is permanent. It's stored in message history. Renames need @deprecated aliases (see packages/builtin-tools/src/inspectors.ts:88-89). Get it right the first time.
  2. ApiName is an as const object, not a TS enum. It doubles as the runtime list BaseExecutor iterates over.
  3. Three result fields, three audiences:
    • content: string → the LLM reads it
    • state: Record<…> → the UI's pluginState; result-domain only, never echo all params back
    • error: { type, message, body? } → both LLM and UI; type is a stable code
  4. Split execution from frontend wiring.
    • src/ExecutionRuntime/ — pure runtime, no React, no Zustand, accepts services via constructor. The default place for new logic.
    • src/client/executor/BaseExecutor subclass that calls ExecutionRuntime (or stores/services directly when frontend-only).
  5. UI defaults to "do nothing". Inspector is required (the header strip). Render/Placeholder/Streaming/Intervention/Portal are added only when there's something specific to show — empty registries are fine.
  6. Style with createStaticStyles + cssVar.* (zero-runtime). Fall back to createStyles + token only when you genuinely need runtime values. Use @lobehub/ui components, not raw antd.
  7. i18n keys live in packages/locales/src/default/plugin.ts. Inspector titles must come from t('builtins.<identifier>.apiName.<api>') so something renders while args stream.

Package Layout (preferred, post-2026 convention)

packages/builtin-tool-<name>/
├── package.json
└── src/
    ├── index.ts              # exports manifest + types + systemRole + Identifier (no React, no stores)
    ├── manifest.ts           # BuiltinToolManifest with JSON Schema for every API
    ├── types.ts              # ApiName const + Params/State interfaces per API
    ├── systemRole.ts         # System prompt teaching the model when/how to use the APIs
    ├── ExecutionRuntime/     # ✅ Default home for runtime logic (server- or anywhere-callable)
    │   └── index.ts
    └── client/
        ├── index.ts          # Re-exports for the registries
        ├── executor/         # ✅ Frontend executor — extends BaseExecutor, often delegates to ExecutionRuntime
        │   └── index.ts
        ├── Inspector/        # required — header chip per API
        ├── Render/           # optional — rich result card
        ├── Placeholder/      # optional — skeleton during streaming/execution
        ├── Streaming/        # optional — live output renderer (e.g. RunCommand, WriteFile)
        ├── Intervention/     # optional — approval / edit-before-run UI
        ├── Portal/           # optional — full-screen detail view
        └── components/       # shared subcomponents used by the surfaces above

Older packages (builtin-tool-task, builtin-tool-calculator, etc.) still have src/executor/ as a sibling of src/client/. That's grandfathered; don't relocate without a deliberate refactor. New packages and new APIs added to existing packages should follow the layout above.

package.json exports map:

"exports": {
  ".":                  "./src/index.ts",
  "./client":           "./src/client/index.ts",
  "./executor":         "./src/client/executor/index.ts",
  "./executionRuntime": "./src/ExecutionRuntime/index.ts"
}

Authoring Checklist

Before opening the PR:

  • Identifier follows lobe-<domain> and is stable (lives in message history).
  • Every <Name>ApiName value has: a manifest api[] entry, an executor method, an Inspector, an i18n apiName.* key.
  • Params interfaces match the JSON Schema; State interfaces match what the executor returns and what the UI surfaces read.
  • System prompt disambiguates confusable APIs and points to batch variants.
  • Runtime logic lives in ExecutionRuntime/; the client/executor/ only wires stores/services and delegates.
  • Executor returns { success, content, state, error? } via a single toResult() funnel — content always non-empty (default to error.message).
  • Inspector handles isArgumentsStreaming, isLoading, partialArgs, missing pluginState.
  • Render returns null until it has data; only created for APIs with rich results.
  • Placeholder added if the API has a perceivable execution lag (search, list, crawl).
  • Streaming added for APIs that emit incremental output (run command, write file, code execution).
  • Intervention added if humanIntervention is set in the manifest.
  • All registry files updated (see architecture.md → Registry wiring).
  • i18n keys in packages/locales/src/default/plugin.ts plus dev seeds in en-US/zh-CN.
  • bunx vitest run --silent='passed-only' 'packages/builtin-tool-<name>' passes.
  • bun run type-check passes.

Reference Tools

Pick the closest neighbor and copy:

If your tool is…Read first
Pure-compute, no UI statepackages/builtin-tool-calculator/ExecutionRuntime reuses executor (mathjs/nerdamer work everywhere)
CRUD over a domain entitypackages/builtin-tool-task/ — full Inspector + Render set, batch variants
Heavy UI (Inspector/Render/Placeholder/Portal)packages/builtin-tool-web-browsing/ — search-style result UI, Portal for detail view
Desktop / filesystem with all surfaces (incl. Streaming + Intervention)packages/builtin-tool-local-system/ExecutionRuntime injects an ILocalSystemService, executor calls it
Server-side pure (no client executor)packages/builtin-tool-web-browsing/ — only ExecutionRuntime is exported; the chat client doesn't run it
Needs human approval before runningpackages/builtin-tool-local-system/src/client/Intervention/ — per-API approval components

Signals

GitHub stars
82k
Forks
16k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
builtin-tool-lobehub
Source
github.com/lobehub/lobehub