pi-tool-repair 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +1 -1
- package/src/grammar-repair.ts +40 -0
- package/tool-repair.ts +6 -4
package/README.md
CHANGED
|
@@ -145,6 +145,24 @@ Modes:
|
|
|
145
145
|
| `recover` | Strip leaked markup and append recovered pi `toolCall` blocks. |
|
|
146
146
|
| `strip` | Strip leaked markup only; do not execute recovered calls. |
|
|
147
147
|
|
|
148
|
+
#### Per-model enablement
|
|
149
|
+
|
|
150
|
+
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:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"grammarRepair": {
|
|
155
|
+
"leakModels": ["kimi", "qwen3", "gguf"]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
#### What the model sees on the next request
|
|
163
|
+
|
|
164
|
+
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.
|
|
165
|
+
|
|
148
166
|
Safety gates:
|
|
149
167
|
|
|
150
168
|
- `requireKnownTool: true` only recovers calls whose name is in pi's active tool registry.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-tool-repair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Validate-then-repair extension for pi — fixes common LLM tool-call mistakes (null fields, stringified arrays, wrong field names, anchor bleed) before tools execute",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tom X Nguyen",
|
package/src/grammar-repair.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface GrammarRepairConfig {
|
|
|
24
24
|
mode: GrammarRepairMode;
|
|
25
25
|
requireKnownTool: boolean;
|
|
26
26
|
debug: boolean;
|
|
27
|
+
// Case-insensitive regex fragments that auto-enable grammar repair when the
|
|
28
|
+
// active model id matches one of them. Ignored while `enabled` is true.
|
|
29
|
+
leakModels?: string[];
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export interface ExtensionFileConfig {
|
|
@@ -119,15 +122,52 @@ export function normalizeGrammarRepairConfig(raw: Partial<GrammarRepairConfig> =
|
|
|
119
122
|
? raw.grammars.filter((name): name is GrammarName => grammarSet.has(name as GrammarName))
|
|
120
123
|
: ALL_GRAMMARS;
|
|
121
124
|
|
|
125
|
+
const leakModels = normalizeLeakModels(raw.leakModels);
|
|
126
|
+
|
|
122
127
|
return {
|
|
123
128
|
enabled: raw.enabled ?? DEFAULT_GRAMMAR_REPAIR_CONFIG.enabled,
|
|
124
129
|
grammars: grammars.length > 0 ? grammars : ALL_GRAMMARS,
|
|
125
130
|
mode: raw.mode === "strip" ? "strip" : "recover",
|
|
126
131
|
requireKnownTool: raw.requireKnownTool ?? DEFAULT_GRAMMAR_REPAIR_CONFIG.requireKnownTool,
|
|
127
132
|
debug: raw.debug ?? DEFAULT_GRAMMAR_REPAIR_CONFIG.debug,
|
|
133
|
+
...(leakModels ? { leakModels } : {}),
|
|
128
134
|
};
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
const compileLeakModelPattern = (pattern: string): RegExp | undefined => {
|
|
138
|
+
try {
|
|
139
|
+
return new RegExp(pattern, "i");
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
function normalizeLeakModels(raw: unknown): string[] | undefined {
|
|
146
|
+
if (!Array.isArray(raw)) return undefined;
|
|
147
|
+
const valid = raw.filter(
|
|
148
|
+
(pattern): pattern is string =>
|
|
149
|
+
typeof pattern === "string" && compileLeakModelPattern(pattern) !== undefined,
|
|
150
|
+
);
|
|
151
|
+
return valid.length > 0 ? valid : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Enables grammar repair for the current message when the active model id
|
|
155
|
+
// matches a configured leakModels pattern. Global `enabled: true` always wins,
|
|
156
|
+
// so models with native tool calling keep the recovery path off unless listed.
|
|
157
|
+
export function resolveGrammarRepairForModel(
|
|
158
|
+
config: GrammarRepairConfig,
|
|
159
|
+
model: { id?: string } | null | undefined,
|
|
160
|
+
): GrammarRepairConfig {
|
|
161
|
+
if (config.enabled) return config;
|
|
162
|
+
const patterns = config.leakModels;
|
|
163
|
+
if (!patterns || patterns.length === 0) return config;
|
|
164
|
+
const modelId = model?.id;
|
|
165
|
+
if (typeof modelId !== "string" || modelId.length === 0) return config;
|
|
166
|
+
return patterns.some((pattern) => compileLeakModelPattern(pattern)?.test(modelId))
|
|
167
|
+
? { ...config, enabled: true }
|
|
168
|
+
: config;
|
|
169
|
+
}
|
|
170
|
+
|
|
131
171
|
export function repairAssistantMessageGrammarLeaks(
|
|
132
172
|
message: MinimalAssistantMessage,
|
|
133
173
|
config: GrammarRepairConfig,
|
package/tool-repair.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
logRepair,
|
|
18
18
|
loadGrammarRepairConfig,
|
|
19
19
|
repairAssistantMessageGrammarLeaks,
|
|
20
|
+
resolveGrammarRepairForModel,
|
|
20
21
|
repairAssistantToolCallInputs,
|
|
21
22
|
normalizePhantomToolUse,
|
|
22
23
|
type MinimalAssistantMessage,
|
|
@@ -117,25 +118,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
117
118
|
if (model && hasAnchorBleedBug(model) && stripAnchorBleedInPlace(args)) changed = true;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
const effectiveGrammarRepairConfig = resolveGrammarRepairForModel(grammarRepairConfig, model);
|
|
122
|
+
if (effectiveGrammarRepairConfig.enabled) {
|
|
121
123
|
const knownTools = new Set(
|
|
122
124
|
safeGetActiveTools(pi)
|
|
123
125
|
.filter((name): name is string => typeof name === "string" && name.length > 0),
|
|
124
126
|
);
|
|
125
127
|
const grammarResult = repairAssistantMessageGrammarLeaks(
|
|
126
128
|
message,
|
|
127
|
-
|
|
129
|
+
effectiveGrammarRepairConfig,
|
|
128
130
|
knownTools,
|
|
129
131
|
);
|
|
130
132
|
if (grammarResult.changed) {
|
|
131
133
|
message = grammarResult.message;
|
|
132
134
|
changed = true;
|
|
133
|
-
if (
|
|
135
|
+
if (effectiveGrammarRepairConfig.debug) {
|
|
134
136
|
const calls = grammarResult.recoveredCalls
|
|
135
137
|
.map((call) => `${call.grammar}:${call.name}`)
|
|
136
138
|
.join(",") || "none";
|
|
137
139
|
process.stderr.write(
|
|
138
|
-
`[pi-tool-repair] grammar-repair mode=${
|
|
140
|
+
`[pi-tool-repair] grammar-repair mode=${effectiveGrammarRepairConfig.mode} ` +
|
|
139
141
|
`stripped=${grammarResult.strippedRanges} recovered=${calls}\n`,
|
|
140
142
|
);
|
|
141
143
|
}
|