pi-tool-repair
Validate-then-repair extension for pi — fixes common LLM tool-call mistakes (null fields, stringified arrays, wrong field names, anchor bleed) before tools execute
Package details
Install pi-tool-repair from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-tool-repair- Package
pi-tool-repair- Version
0.1.1- Published
- Jun 4, 2026
- Downloads
- not available
- Author
- monotykamary
- License
- MIT
- Types
- extension
- Size
- 83.9 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./tool-repair.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
🔧 pi-tool-repair
Validate-then-repair for pi
Fixes the finite set of tool-call mistakes open models make — before tools execute.
Open models aren't bad at tool calling — the harness is.
By adding a thin repair layer, DeepSeek V4 Pro beat Opus 4.7 in 6/10 internal evals — without changing the model. The same four mistakes repeat across DeepSeek, GLM, Qwen, and others. Each fix is 30–100 lines. Order matters.
Reverse-engineered from Command Code's tool parsing pipeline.
What it fixes
| Problem | Model sends | After repair |
|---|---|---|
null for optional fields |
{"path": "/foo", "offset": null} |
{"path": "/foo"} |
| Arrays as JSON strings | "[\"a\",\"b\"]" |
["a","b"] |
{} where array expected |
{"include": {}} |
(dropped) |
| Bare string → array | "foo" |
["foo"] |
| Wrong field names | {"file_path": "/foo"} |
{"path": "/foo"} |
| Bare string as root input | "/path/to/file" |
{"path": "/path/to/file"} |
| Schema anchor bleed (Kimi K2) | "^pattern$" in values |
"pattern" |
| Leaked tool grammar (opt-in) | <|DSML|tool_calls>... |
pi toolCall block |
Install
With pi install (recommended):
pi install https://github.com/monotykamary/pi-tool-repair
With npm:
npm install pi-tool-repair
Manual — add to ~/.pi/agent/settings.json:
{
"packages": ["git:github.com/monotykamary/pi-tool-repair"]
}
Local development — add the extension path directly:
{
"extensions": ["./path/to/pi-tool-repair/tool-repair.ts"]
}
Reload with /reload after any install method.
How it works
┌────────────────────────────────────────────────────────────┐
│ Phase 0: Schema poisoning (before_provider_request) │
│ │
│ Strip regex anchors from JSON Schema patterns for models │
│ where they leak into generated values (Kimi K2, MiniMax) │
│ │
│ Fixes what YOU send the model — not what the model sends │
└──────────────────────────┬─────────────────────────────────┘
│
▼
Model generates tool call
│
▼
┌────────────────────────────────────────────────────────────┐
│ Phase 1: Grammar leak repair (message_end, opt-in) │
│ │
│ Detect raw XML/sentinel tool grammars emitted as text or │
│ thinking, strip them from visible output, and recover them │
│ as pi toolCall blocks when complete and safe. │
└──────────────────────────┬─────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Phase 2: Validate-then-repair (tool_call) │
│ │
│ 1. Validate input against schema (if known tool) │
│ ↳ Valid? Ship it untouched. │
│ 2. Walk the validator's issue list │
│ ↳ Apply targeted repairs only at the exact failed paths │
│ 3. Re-validate the repaired input │
│ ↳ Still invalid? Let the tool handle it. │
│ 4. Log outcome (debug mode) │
└────────────────────────────────────────────────────────────┘
Repair rules (in order)
Order matters — parseJsonStringifiedArray must run before wrapBareStringAsArray or you get double-wrapping.
| # | Rule | What it catches |
|---|---|---|
| 1 | renameAliasedField |
file_path → path, query → pattern, etc. |
| 2 | dropNullOrUndefined |
null/undefined for optional fields |
| 3 | dropEmptyObjectPlaceholder |
{} where array expected |
| 4 | parseJsonStringifiedArray |
"[\"a\",\"b\"]" → ["a","b"] |
| 5 | wrapBareStringAsArray |
"foo" → ["foo"] |
| 6 | wrapRootStringAsObject |
"/path" → {"path": "/path"} |
Why validate-then-repair (not preprocess-then-validate)
Preprocessing inputs before validation silently corrupts valid data — rewriting file content that happened to look like JSON, for example. The better design:
- Parse the input as-is. If valid, ship it untouched.
- On failure, walk the validator's issue list and apply repairs only at the exact paths that failed.
- Re-validate. The schema localizes the bug for you — you only spend repair effort where it's actually needed.
Configuration
Grammar leak repair (disabled by default)
Raw XML/sentinel tool-call grammar recovery is opt-in because it can turn assistant text into tool execution. Enable it in ~/.pi/agent/extensions/pi-tool-repair.json:
{
"grammarRepair": {
"enabled": true,
"mode": "recover",
"requireKnownTool": true,
"grammars": [
"dsml",
"invoke",
"qwen",
"kimi",
"mistral",
"llama",
"glm",
"granite",
"minimax-text",
"olmo"
]
}
}
Modes:
| Mode | Behavior |
|---|---|
recover |
Strip leaked markup and append recovered pi toolCall blocks. |
strip |
Strip leaked markup only; do not execute recovered calls. |
Safety gates:
requireKnownTool: trueonly recovers calls whose name is in pi's active tool registry.- Markup inside fenced code blocks is ignored so syntax discussions and examples are preserved.
- Incomplete or unparseable blocks are left alone.
- If the provider already emitted native
toolCallblocks, leaked shadow text is stripped but duplicate calls are not added.
Covered grammar families: DeepSeek DSML, MiniMax/Anthropic <invoke>, Qwen/Hermes <tool_call>, Kimi sentinels, Mistral [TOOL_CALLS], Llama <|python_tag|>, GLM arg_key/arg_value, Granite JSON <tool_call>, MiniMax-Text-01 TypeScript calls, and OLMo3 <function_calls> pythonic calls. See docs/tool-call-grammar-leakage-survey.md for the survey.
Debug logging
Set PI_TOOL_REPAIR_DEBUG=1 or grammarRepair.debug: true to log repair diagnostics to stderr:
[pi-tool-repair] tool=read outcome=recovered rules=dropNullOrUndefined hints=1
input: {"path":"/foo","offset":null}
repaired: {"path":"/foo"}
hint[0]: Dropped null `offset` from tool "read"...
Covered tools
Repair rules apply to pi's built-in tools: read, write, edit, bash, grep, find, ls.
Anchor bleed models
Phase 0 schema sanitization activates for models matching these patterns:
| Pattern | Models |
|---|---|
/kimi-k2/i |
Kimi K2 variants |
/minimax/i |
MiniMax variants |
/glm/i |
GLM variants |
To add more models, edit anchorBleedModels in src/index.ts.
Field aliases
The extension maps common model mistakes (wrong field names) to the canonical field name. For example, when calling read, the model can send file_path, absolutePath, filepath, target_file, etc. — all map to path.
| Tool | Canonical | Aliases |
|---|---|---|
read |
path |
absolutePath, file_path, filePath, filepath, pathname, target_file, targetFile, file, absolute_path, fileAbsolutePath |
grep |
pattern |
query, regex, search, q, expression, text |
write |
path |
absolutePath, file_path, filePath, filepath, pathname, target_file, targetFile |
write |
content |
text, body, data, contents, fileContent |
edit |
path |
absolutePath, file_path, filePath, filepath, pathname, target_file, targetFile |
edit |
oldText |
old_string, oldString, old, old_str, oldStr, from, old_value, oldText, old_text, oldContent, old_content |
edit |
newText |
new_string, newString, new, new_str, newStr, to, new_value, newText, new_text, newContent, new_content |
ls |
path |
absolutePath, directory, dir, folder, directoryPath |
find |
pattern |
query, glob, expression, search, include |
bash |
command |
cmd, shell, script, commandLine |
Development
npm install
npm test # run tests
npm run test:watch # watch mode
npm run test:coverage # coverage report
npm run typecheck # type checking
npm run lint:dead # dead code detection
Related projects
| Project | Description |
|---|---|
| pi-retry | Automatic retry for 400/413/connection errors |
| pi-fast-resume | Instant session picker (6ms vs 5.6s) |
| pi-hide-providers | Hide providers and models from the selector |
| pi-double-esc | Prevent accidental Escape aborts |
| pi-loop | Close the verification loop on task completion |
| pi-fireworks-provider | Fireworks AI provider (origin of the Kimi anchor-bleed fix) |