pi-codemode-continue0.1.0
Pi extension that keeps the run going when the model writes a tool call as plain text instead of calling the tool.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
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 theagent_before_settleboundary). 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