| name | pi-tool-repair-integration | ||||
|---|---|---|---|---|---|
| description | Build or wrap tools in a pi coding-agent extension using @r3b1s/pi-repair-layer's adaptToolDefinition and the optional-dependency fallback pattern. Use when authoring a pi extension that registers tools, when adding argument repair (aliases, envelope recovery, schema-guided fixes) to an extension-owned tool, or when deciding whether pi-repair-layer should be a hard or optional dependency. Triggers on pi extensions, registerTool, prepareArguments, tool-call repair, or pi-repair-layer integration tasks. | ||||
| license | MIT | ||||
| metadata |
|
Integrate @r3b1s/pi-repair-layer into a pi extension's own tools so
malformed model arguments ({file_path: "/x"} instead of {path: "/x"},
stringified JSON, stray nulls) are repaired before pi validation — without
forcing every downstream user to install the repair layer.
Verified baselines: package ^0.3.0, Node 22+, pi 0.80.6 (probed through
0.80.10).
pi runs prepareArguments → validation → tool_call → execute. Arguments that
fail validation never reach tool_call, so repair must run in
prepareArguments — and only the extension that owns a tool definition can
install that hook. Installing pi-repair-layer repairs pi's built-in tools
only; it never discovers or wraps tools registered by other extensions. Your
extension must opt in per tool. Never attempt to wrap another extension's
tools.
| Posture | When | How |
|---|---|---|
| Optional (default for standalone extensions) | The tool works fine unwrapped; repairs are an enhancement | Fallback recipe below; package stays out of runtime deps |
| Hard dependency | The tool relies on repair behavior (e.g. legacy-shape migration via preprocessors) | pnpm add @r3b1s/pi-repair-layer; import statically |
The optional posture turns "also install pi-repair-layer" into an end-user
opt-in: pi installs all npm: extensions into one shared node_modules per
scope, so a user who runs pi install npm:@r3b1s/pi-repair-layer makes it
resolvable to every consenting npm-installed extension automatically.
Repair options must be plain data validated with a type-only import, so compilation never requires the package at runtime:
import type { PiToolOwnerAdapterOptions } from "@r3b1s/pi-repair-layer/pi";
const repairOptions = {
policy: "adaptive",
preprocessors: [
{
kind: "alias",
selector: "/path",
aliases: ["file_path"],
accepts: "string",
},
],
} satisfies PiToolOwnerAdapterOptions;Configure only transforms you know are safe for your tool — see
resources/preprocessor-catalog.md for
every kind, selector semantics, and the policy profiles. The pipeline never
guesses aliases, fuzzily renames keys, or deletes unknown fields; wrapped
tools get bounded envelope recovery and schema-located repairs for free.
Copy resources/optional-extension-template.ts (a complete extension) and adapt names. The load-bearing core:
import type { adaptToolDefinition } from "@r3b1s/pi-repair-layer/pi";
async function loadRepairAdapter(): Promise<
typeof adaptToolDefinition | undefined
> {
try {
const repair = await import("@r3b1s/pi-repair-layer/pi");
return repair.adaptToolDefinition;
} catch (error) {
const code = (error as { code?: unknown } | null)?.code;
const message = error instanceof Error ? error.message : String(error);
const packageAbsent =
(code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") &&
message.includes("@r3b1s/pi-repair-layer");
if (!packageAbsent) throw error;
return undefined;
}
}
export default async function myExtension(pi: ExtensionAPI) {
const adapt = await loadRepairAdapter();
if (!adapt) {
console.error(
"[my-extension] @r3b1s/pi-repair-layer not found; my_tool running unwrapped",
);
}
pi.registerTool(adapt ? adapt(definition, repairOptions) : definition);
}Every detail matters — do not simplify these away:
- Both error codes.
MODULE_NOT_FOUNDis jiti's require path;ERR_MODULE_NOT_FOUNDis native ESM and the compiled pi binary. - The message must name
@r3b1s/pi-repair-layer. A present-but-broken install throws the same codes naming a transitive module; swallowing it would silently disable repairs the user believes are active. Match the package name, not the/pisubpath — native ESM reports onlyCannot find package '@r3b1s/pi-repair-layer'. - Rethrow anything else. Any other error is a real failure, not absence.
- One stderr note on fallback. The branches differ in coercion behavior; a silent divergence cannot be diagnosed from a session transcript.
- Identity fallback. Register the unmodified definition — never a partial wrapper.
Alternative: authors who want repairs whenever the environment allows can add
"optionalDependencies": { "@r3b1s/pi-repair-layer": "^0.3.0" } — a failed
optional install does not fail the extension install. This is also the path
for consumers the shared-root story cannot reach (see caveats).
Work through resources/testing-checklist.md. Minimum bar: with the package absent, activation succeeds, the raw definition is registered, and the note is emitted; with it present, the adapter branch is taken silently and each configured repair produces valid arguments.
- Compiled pi binary never takes the adapter branch. Under the standalone
(Bun-compiled) pi executable, dynamic
import()cannot resolve npm-installed siblings, so the recipe falls back — safely, with the note — even when the package is installed. The optional pattern activates only under Node-based pi installs. A hard static dependency works under both. - Scope and install source matter. Git-installed extensions get their own
clone-local
node_modules; project-scope and user-scope installs do not see each other's siblings. Those consumers fall back; offeroptionalDependenciesinstead. - Fallback mode is baseline pi, not "repairs minus notes." pi's native
validation runs TypeBox
Value.Convertfirst, which silently coerces some invalid input (null→"null") instead of repairing or rejecting it. The tool owner must decide explicitly whether that is acceptable. - No double-wrap. The installable pi-repair-layer extension only overrides pi's built-ins; a tool adapted by this recipe is wrapped exactly once in either branch.
- Subpaths (
/pi,/core,/grammar) and theadaptToolDefinition(definition, options?)signature are stable for the current major (semver). - Absence detection semantics (the two codes + module-naming message) are part of the contract.
- Unrecognized preprocessor
kinds are ignored — never fatal, no mutation, results still schema-validated — so options written against a newer minor degrade gracefully on older installs.
- Failing closed: the adapter throws
UnrepairableToolInputErrorwith a model-readable retry message by default;unrepairable: "passthrough"exists only for deliberate migrations. - Structured outcomes: pass
onOutcome(result)for value-free metrics (rule IDs, stages, policy, fingerprint — never argument values). <repair_note>feedback and theRepairLifecycle, plus the pure-corerunRepairPipeline, are documented in the package'sdocs/tool-owner-integration.md.
{ "devDependencies": { "@r3b1s/pi-repair-layer": "^0.3.0" // typecheck + local tests only }, "peerDependencies": { "@r3b1s/pi-repair-layer": ">=0.3.0" }, "peerDependenciesMeta": { "@r3b1s/pi-repair-layer": { "optional": true } } }