pi-codemode-continue

Pi extension that keeps the run going when the model writes a tool call as plain text instead of calling the tool.

Packages

Package details

extension

Install pi-codemode-continue from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-codemode-continue
Package
pi-codemode-continue
Version
0.1.0
Published
Nov 4, 2026
Downloads
345/mo · 345/wk
Author
quantmind-br
License
MIT
Types
extension
Size
22.3 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-codemode-continue

A pi extension that keeps the run going when the model writes a tool call as plain text instead of calling the tool.

The problem

Local models sometimes emit tool-call markup that the inference server cannot parse, for example:

<tool_call>
<function=describe_image>
<parameter=image_path>
/tmp/shot.png
</parameter>
</function>
</tool_call>

The usual cause is a call to a tool that was not declared in the request. With codemode.mode: "only", pi declares only codemode (and model-only tools such as ask_user_question); every other tool is reachable only as tools.<name>() inside a script. When the model calls one of those directly, the server's tool-call parser does not recognize the function and returns the markup as ordinary text with finish_reason: "stop". Pi sees a final answer without tool calls and ends the run.

Pi already recovers from the other tool-call errors on its own: an unknown tool name or invalid arguments in a real tool call come back to the model as an error tool result.

What it does

When a run is about to end on such a reply, the extension appends a visible notice and asks pi for one more model call. The notice tells the model that the call did not run and how to make it:

  • a tool reachable only from codemode gets a ready-made script with the arguments the model wrote:
    const result = await tools.describe_image({"image_path":"/tmp/shot.png"});
    console.log(result);
  • a declared tool is asked for again as a real tool call;
  • an unknown tool is named as such;
  • the notice always lists the tools the model can call directly.

It recognizes the markup of Qwen/Hermes (<tool_call>), Qwen3-Coder XML (<function=…>), Anthropic-style XML (<invoke name=…>), Kimi, DeepSeek, Mistral ([TOOL_CALLS]), Llama 3 (<|python_tag|>), gpt-oss (to=functions.…), GLM, MiniMax and Seed, and replies that are nothing but a JSON tool call. Markup quoted in code spans of a normal answer does not count. The reasoning is scanned only when the reply is empty.

When it acts

Guard Why
The run ended normally Never after Esc (abort) or a provider error; pi retries provider errors itself.
The run ended on an assistant message A tool that returns terminate: true stopped the run on purpose.
That message has tool-call markup and no real tool call Anything else is a normal answer.
Fewer than 3 consecutive notices Hard cap against loops. The count resets after a real tool call or a user message, so a long run that recovers each time keeps its retries.

Another extension's continuation (for example pi-todo-stop-reminder) or queued messages do not suppress the notice: without it the model would continue without knowing that its call never ran.

The extension keeps no state. It counts its own notices on the session branch, so the limit survives reloads, branching, and compaction.

Requirements

  • pi >= 1.2.0 (uses the agent_before_settle boundary). Tested with pi 1.5.0.

Install

pi install npm:pi-codemode-continue

Or from git, or from a local clone:

pi install git:github.com/quantforge-br/pi-codemode-continue
pi install ./pi-codemode-continue

Install it from one source only. Pi identifies npm packages by name and local packages by path, so an npm entry and a local entry for this package load two copies, and the model gets two notices for each leaked call.

Or try it for one session:

pi -e npm:pi-codemode-continue

Configuration

Variable Default Effect
PI_CODEMODE_CONTINUE on off, 0, false, or no disables the extension.
PI_CODEMODE_CONTINUE_MAX 3 Maximum consecutive notices. 0 disables the extension.
PI_CODEMODE_CONTINUE_DEBUG unset 1 logs every decision to stderr.

Limitations

  • It does not execute the leaked call. Turning text back into a tool call would mean guessing argument types from XML and rewriting direct calls into codemode scripts; one extra model call is the safer price.
  • A normal answer that writes tool-call markup outside code spans is treated as a leak. The notice tells the model to reply again without markup in that case, and the cap bounds the cost.

Development

npm install
npm run check   # tsc --noEmit + node --test

The tests run on Node's built-in TypeScript type stripping (Node >= 22.18).

License

MIT