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

Packages

Package details

extension

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.2.5
Published
Sep 5, 2026
Downloads
2,420/mo · 633/wk
Author
monotykamary
License
MIT
Types
extension
Size
99.9 KB
Dependencies
0 dependencies · 2 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.

pi extension license


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 {"edits":"[{...}]"} {"edits":[{...}]}
{} where an optional array is expected {"include":{}} (dropped)
Bare string where an array is expected {"include":"foo"} {"include":["foo"]}
Wrong field names {"file_path":"/foo"} {"path":"/foo"}
Numeric strings {"limit":"20"} {"limit":20}
Bare string as root input "/path/to/file" {"path":"/path/to/file"}
fabric_exec code arrays {"code":["const x=1;","return x;"]} newline-joined code
Schema anchor bleed (Kimi K2) "^pattern$" in values "pattern"
Leaked tool grammar (opt-in) <|DSML|tool_calls>... pi toolCall block
Phantom tool use stopReason:"toolUse" with no call retryable error

Install

With pi install (recommended):

pi install npm:pi-tool-repair

Or install from GitHub:

pi install https://github.com/monotykamary/pi-tool-repair

With npm:

npm install npm: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

before_provider_request
  └─ model-gated schema anchor sanitization

model response → message_end
  ├─ strip leaked grammar tokens from native toolCall blocks
  ├─ recover complete leaked grammar calls when enabled
  ├─ validate raw arguments against each active tool's live schema
  ├─ apply only known aliases and schema-directed repairs
  ├─ commit a candidate only when it re-validates
  └─ turn phantom toolUse responses into retryable errors

Pi then runs its normal prepare → validate → execute pipeline

Pi 0.84 validates tool arguments before emitting tool_call. Repair therefore runs on the finalized assistant message, while the provider's raw arguments are still available and before Pi's validation can reject or coerce them.

Repair rules

Rule What it catches
renameAliasedField file_pathpath, querypattern, option aliases, etc.
dropNullOrUndefined null/undefined for schema-optional fields
dropEmptyObjectPlaceholder {} where an optional array is expected
parseJsonStringifiedArray "[\"a\",\"b\"]"["a","b"]
wrapBareStringAsArray "foo"["foo"] when the schema expects an array
wrapRootStringAsObject "/path"{"path":"/path"} for known string-primary tools
coerceNumericString "20"20 when the live schema expects a number
convertTimeoutMilliseconds timeoutMstimeout seconds for bash
joinStringArray all-string fabric_exec.code arrays → one newline-joined string

Why validate-then-repair

The extension reads the schemas from pi.getAllTools() instead of maintaining a parallel copy. Schema-valid input with no known compatibility aliases is returned unchanged. Invalid input is cloned, repaired only at schema-declared fields, and revalidated; an unrepairable candidate is discarded so Pi reports the original error. Canonical fields win when both canonical and alias spellings are present.

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.

Per-model enablement

If only some of your models leak grammar — common with local servers such as llama.cpp, vLLM, or Ollama — auto-enable recovery per model id with leakModels. Entries are case-insensitive regex fragments matched against the active model id:

{
  "grammarRepair": {
    "leakModels": ["kimi", "qwen3", "gguf"]
  }
}

Recovery turns on whenever the session's model id matches a pattern. Global enabled: true takes precedence over leakModels, so models with reliable native tool calling stay untouched. Regex entries that fail to compile are ignored.

What the model sees on the next request

Every repair runs on pi's message_end hook, where the repaired message is replaced in place — the corrected call, not the model's original output, is what pi writes to the session file and resends on later requests. With mode: "recover", leaked tool-call text is likewise converted into real toolCall blocks before persistence, so subsequent requests show the model a properly formed call plus its tool results: an in-context correction loop instead of a silent execute-time patch. Local models benefit the most since there is no prompt-cache penalty for the rewritten history; providers that cache by prefix may treat the first turn after a repair as a cache miss.

Safety gates:

  • requireKnownTool: true only 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 not recovered as tool calls. For DSML, dangling or truncated marker tokens (e.g. a stream that died at <|DSML|tool_calls with no closing >) are still stripped from visible text so the raw marker doesn't persist in the transcript.
  • If the provider already emitted native toolCall blocks, 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

Schema-directed null, array, and numeric repairs apply to every active tool whose live schema Pi exposes. The curated alias table covers Pi's built-in read, write, edit, bash, grep, find, and ls tools. fabric_exec root strings and all-string code arrays are also supported.

Pi Fabric compatibility

With Pi Fabric full code mode, pi-tool-repair sees fabric_exec as the active model-facing tool. It repairs the outer call, leaked provider grammars, anchor bleed, and phantom tool-use responses before Pi validation. Nested pi.* calls are created later by Fabric's TypeScript guest, so Fabric owns their alias and optional-null normalization before its registry validation. No duplicate tool registration or wrapper is required.

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
read offset, limit start, max
grep pattern query, regex, search, q, expression, text
grep glob, ignoreCase, context, limit globPattern, ic, caseInsensitive, ctx, max
write path, content path aliases above; text, body, data, contents, fileContent
edit path path aliases above
edit oldText old_string, oldString, old, old_str, oldStr, from, old_value, old_text, oldContent, old_content
edit newText new_string, newString, new, replacement, new_str, newStr, to, new_value, new_text, newContent, new_content
ls path, limit path aliases plus directory, dir, folder, directoryPath; max
find pattern, limit query, regex, glob, expression, search, include, name, filename; max
bash command cmd, shell, cmdline, script, commandLine

Development

pnpm install
pnpm test              # run tests
pnpm run test:watch    # watch mode
pnpm run test:coverage # coverage report
pnpm run typecheck     # type checking
pnpm 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)

License

MIT