pi-tool-repair 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tool-repair",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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",
@@ -33,13 +33,6 @@
33
33
  "docs/",
34
34
  "README.md"
35
35
  ],
36
- "scripts": {
37
- "test": "vitest run",
38
- "test:watch": "vitest",
39
- "test:coverage": "vitest run --coverage",
40
- "typecheck": "tsc --noEmit",
41
- "lint:dead": "knip --no-gitignore"
42
- },
43
36
  "devDependencies": {
44
37
  "@earendil-works/pi-coding-agent": "0.79.4",
45
38
  "@types/node": "25.9.1",
@@ -52,5 +45,12 @@
52
45
  "extensions": [
53
46
  "./tool-repair.ts"
54
47
  ]
48
+ },
49
+ "scripts": {
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "test:coverage": "vitest run --coverage",
53
+ "typecheck": "tsc --noEmit",
54
+ "lint:dead": "knip --no-gitignore"
55
55
  }
56
- }
56
+ }
package/src/index.ts CHANGED
@@ -131,6 +131,67 @@ export function stripAnchorBleedInPlace(obj: Record<string, unknown>): void {
131
131
  }
132
132
  }
133
133
 
134
+ // Leaked grammar markers from GLM/ChatGLM style tool-call grammars.
135
+ // These can end up as literal prefixes/suffixes on parsed object keys
136
+ // or string values instead of being interpreted as XML tags.
137
+ const GRAMMAR_TOKEN_LEAKS = [
138
+ { tag: "<arg_key>", at: "start" as const },
139
+ { tag: "</arg_key>", at: "end" as const },
140
+ { tag: "<arg_value>", at: "start" as const },
141
+ { tag: "</arg_value>", at: "end" as const },
142
+ ];
143
+
144
+ export function stripGrammarTokenLeaksInPlace(obj: Record<string, unknown>): void {
145
+ for (const key of Object.keys(obj)) {
146
+ const value = obj[key];
147
+ let newKey = key;
148
+ for (const { tag, at } of GRAMMAR_TOKEN_LEAKS) {
149
+ if (at === "start" && newKey.startsWith(tag)) {
150
+ newKey = newKey.slice(tag.length);
151
+ } else if (at === "end" && newKey.endsWith(tag)) {
152
+ newKey = newKey.slice(0, -tag.length);
153
+ }
154
+ }
155
+ newKey = newKey.trim();
156
+
157
+ if (newKey !== key) {
158
+ obj[newKey] = value;
159
+ delete obj[key];
160
+ }
161
+
162
+ if (typeof value === "string") {
163
+ let s = value;
164
+ for (const { tag, at } of GRAMMAR_TOKEN_LEAKS) {
165
+ if (at === "start" && s.startsWith(tag)) {
166
+ s = s.slice(tag.length);
167
+ } else if (at === "end" && s.endsWith(tag)) {
168
+ s = s.slice(0, -tag.length);
169
+ }
170
+ }
171
+ obj[newKey] = s.trim();
172
+ } else if (Array.isArray(value)) {
173
+ for (let i = 0; i < value.length; i++) {
174
+ const item = value[i];
175
+ if (typeof item === "string") {
176
+ let s = item;
177
+ for (const { tag, at } of GRAMMAR_TOKEN_LEAKS) {
178
+ if (at === "start" && s.startsWith(tag)) {
179
+ s = s.slice(tag.length);
180
+ } else if (at === "end" && s.endsWith(tag)) {
181
+ s = s.slice(0, -tag.length);
182
+ }
183
+ }
184
+ value[i] = s.trim();
185
+ } else if (item && typeof item === "object") {
186
+ stripGrammarTokenLeaksInPlace(item as Record<string, unknown>);
187
+ }
188
+ }
189
+ } else if (value && typeof value === "object") {
190
+ stripGrammarTokenLeaksInPlace(value as Record<string, unknown>);
191
+ }
192
+ }
193
+ }
194
+
134
195
  // ─── Phase 2: Repair Rules ────────────────────────────────────────────────────
135
196
 
136
197
  export interface RepairResult {
package/tool-repair.ts CHANGED
@@ -45,6 +45,7 @@ import {
45
45
  hasAnchorBleedBug,
46
46
  sanitizeSchemaAnchors,
47
47
  stripAnchorBleedInPlace,
48
+ stripGrammarTokenLeaksInPlace,
48
49
  wrapRootStringAsObject,
49
50
  repairToolInput,
50
51
  validateAgainstSchema,
@@ -213,6 +214,12 @@ export default function (pi: ExtensionAPI) {
213
214
  }
214
215
  }
215
216
 
217
+ // Defense-in-depth: strip leaked grammar tokens (e.g. GLM <arg_key>) from
218
+ // parsed tool-call keys/values before the schema sees them.
219
+ if (input && typeof input === "object") {
220
+ stripGrammarTokenLeaksInPlace(input as Record<string, unknown>);
221
+ }
222
+
216
223
  // Only repair built-in tools we have schemas for
217
224
  const schema = BUILTIN_SCHEMAS[toolName];
218
225
  if (!schema) return;