@vtstech/pi-react-fallback 1.0.7 → 1.0.8

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/react-fallback.js +87 -56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtstech/pi-react-fallback",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "ReAct fallback extension for Pi Coding Agent",
5
5
  "main": "react-fallback.js",
6
6
  "keywords": ["pi-extensions"],
@@ -14,7 +14,7 @@
14
14
  "url": "https://github.com/VTSTech/pi-coding-agent"
15
15
  },
16
16
  "dependencies": {
17
- "@vtstech/pi-shared": "1.0.7"
17
+ "@vtstech/pi-shared": "1.0.8"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@mariozechner/pi-coding-agent": ">=0.66"
package/react-fallback.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // .build-npm/react-fallback/react-fallback.temp.ts
2
+ import os from "node:os";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
2
5
  import { section, ok, fail, warn, info } from "@vtstech/pi-shared/format";
3
6
  function sanitizeModelJson(text) {
4
7
  text = text.replace(/:\s*True\b/g, ": true");
@@ -273,82 +276,103 @@ function looksLikeSchemaDump(text) {
273
276
  const matches = indicators.filter((i) => lower.includes(i.toLowerCase())).length;
274
277
  return matches >= 2;
275
278
  }
279
+ var REACT_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "react-mode.json");
280
+ function readReactConfig() {
281
+ try {
282
+ if (fs.existsSync(REACT_CONFIG_PATH)) {
283
+ const raw = JSON.parse(fs.readFileSync(REACT_CONFIG_PATH, "utf-8"));
284
+ if (typeof raw.enabled === "boolean") return raw;
285
+ }
286
+ } catch {
287
+ }
288
+ return { enabled: false };
289
+ }
290
+ function writeReactConfig(config) {
291
+ const dir = path.dirname(REACT_CONFIG_PATH);
292
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
293
+ fs.writeFileSync(REACT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
294
+ }
276
295
  function react_fallback_temp_default(pi2) {
277
- let reactModeEnabled = false;
296
+ let reactModeEnabled = readReactConfig().enabled;
278
297
  let stats = { bridgeCalls: 0, fuzzyMatches: 0, argNormalizations: 0, parseFailures: 0 };
279
298
  const branding = [
280
- ` \u26A1 Pi ReAct Fallback Extension v1.0.7`,
299
+ ` \u26A1 Pi ReAct Fallback Extension v1.0.8`,
281
300
  ` Written by VTSTech`,
282
301
  ` GitHub: https://github.com/VTSTech`,
283
302
  ` Website: www.vts-tech.org`
284
303
  ].join("\n");
285
- pi2.registerTool({
286
- name: "tool_call",
287
- label: "Universal Tool Call",
288
- description: `Universal tool call bridge. Use this to call any available tool by specifying its name and arguments as JSON.
304
+ function registerBridgeTool() {
305
+ pi2.registerTool({
306
+ name: "tool_call",
307
+ label: "Universal Tool Call",
308
+ description: `Universal tool call bridge. Use this to call any available tool by specifying its name and arguments as JSON.
289
309
 
290
310
  To use: call tool_call with:
291
311
  - name: the exact tool name (e.g. "bash", "read", "write", "edit")
292
312
  - arguments: a JSON string of the tool's arguments (e.g. '{"command": "ls -la"}')
293
313
 
294
314
  The bridge will match your tool name (fuzzy matching supported) and normalize argument names automatically.`,
295
- promptSnippet: "tool_call - universal bridge for calling any tool",
296
- promptGuidelines: [
297
- "When you need to use a tool but are unsure of the exact name, use tool_call with the tool name and arguments.",
298
- `Example: tool_call(name='bash', arguments='{"command": "ls -la"}')`
299
- ],
300
- parameters: {
301
- type: "object",
302
- properties: {
303
- name: { type: "string", description: "Name of the tool to call (fuzzy matching supported)" },
304
- arguments: { type: "string", description: "Tool arguments as a JSON object string" }
315
+ promptSnippet: "tool_call - universal bridge for calling any tool",
316
+ promptGuidelines: [
317
+ "When you need to use a tool but are unsure of the exact name, use tool_call with the tool name and arguments.",
318
+ `Example: tool_call(name='bash', arguments='{"command": "ls -la"}')`
319
+ ],
320
+ parameters: {
321
+ type: "object",
322
+ properties: {
323
+ name: { type: "string", description: "Name of the tool to call (fuzzy matching supported)" },
324
+ arguments: { type: "string", description: "Tool arguments as a JSON object string" }
325
+ },
326
+ required: ["name", "arguments"]
305
327
  },
306
- required: ["name", "arguments"]
307
- },
308
- execute: async (toolCallId, params, signal, onUpdate, ctx) => {
309
- const p = params;
310
- const requestedName = p.name || "";
311
- const argsStr = p.arguments || "{}";
312
- stats.bridgeCalls++;
313
- let args;
314
- try {
315
- args = JSON.parse(argsStr);
316
- if (typeof args !== "object" || args === null || Array.isArray(args)) {
328
+ execute: async (toolCallId, params, signal, onUpdate, ctx) => {
329
+ const p = params;
330
+ const requestedName = p.name || "";
331
+ const argsStr = p.arguments || "{}";
332
+ stats.bridgeCalls++;
333
+ let args;
334
+ try {
335
+ args = JSON.parse(argsStr);
336
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
337
+ args = { input: argsStr };
338
+ }
339
+ } catch {
317
340
  args = { input: argsStr };
318
341
  }
319
- } catch {
320
- args = { input: argsStr };
321
- }
322
- const allTools = pi2.getAllTools();
323
- let targetToolName = null;
324
- if (allTools.includes(requestedName)) {
325
- targetToolName = requestedName;
326
- } else {
327
- targetToolName = fuzzyMatchToolName(requestedName, allTools);
328
- if (targetToolName) stats.fuzzyMatches++;
329
- }
330
- if (!targetToolName) {
331
- stats.parseFailures++;
342
+ const allTools = pi2.getAllTools();
343
+ let targetToolName = null;
344
+ if (allTools.includes(requestedName)) {
345
+ targetToolName = requestedName;
346
+ } else {
347
+ targetToolName = fuzzyMatchToolName(requestedName, allTools);
348
+ if (targetToolName) stats.fuzzyMatches++;
349
+ }
350
+ if (!targetToolName) {
351
+ stats.parseFailures++;
352
+ return {
353
+ content: [{ type: "text", text: `Error: Unknown tool "${requestedName}". Available tools: ${allTools.join(", ")}` }],
354
+ isError: true
355
+ };
356
+ }
357
+ const normalizedArgs = Object.keys(args).length > 0 ? args : {};
358
+ stats.argNormalizations++;
359
+ const argsJson = JSON.stringify(normalizedArgs);
332
360
  return {
333
- content: [{ type: "text", text: `Error: Unknown tool "${requestedName}". Available tools: ${allTools.join(", ")}` }],
334
- isError: true
335
- };
336
- }
337
- const normalizedArgs = Object.keys(args).length > 0 ? args : {};
338
- stats.argNormalizations++;
339
- const argsJson = JSON.stringify(normalizedArgs);
340
- return {
341
- content: [{
342
- type: "text",
343
- text: `[ReAct Bridge] Tool resolved: ${requestedName} \u2192 ${targetToolName}${targetToolName !== requestedName ? " (fuzzy matched)" : ""}
361
+ content: [{
362
+ type: "text",
363
+ text: `[ReAct Bridge] Tool resolved: ${requestedName} \u2192 ${targetToolName}${targetToolName !== requestedName ? " (fuzzy matched)" : ""}
344
364
 
345
365
  Please call ${targetToolName} with these arguments:
346
366
  ${argsJson}`
347
- }],
348
- isError: false
349
- };
350
- }
351
- });
367
+ }],
368
+ isError: false
369
+ };
370
+ }
371
+ });
372
+ }
373
+ if (reactModeEnabled) {
374
+ registerBridgeTool();
375
+ }
352
376
  pi2.on("context", (event) => {
353
377
  if (!reactModeEnabled) return;
354
378
  const model = event.messages;
@@ -367,18 +391,25 @@ ${argsJson}`
367
391
  description: "Toggle ReAct fallback mode for models without native tool calling",
368
392
  handler: async (_args, ctx) => {
369
393
  reactModeEnabled = !reactModeEnabled;
394
+ writeReactConfig({ enabled: reactModeEnabled });
370
395
  const status = reactModeEnabled ? "ENABLED" : "DISABLED";
371
396
  ctx.ui.notify(`ReAct mode ${status}`, "success");
372
397
  const lines = [branding];
373
398
  lines.push(section("REACT FALLBACK MODE"));
374
399
  lines.push(info(`Status: ${status}`));
400
+ lines.push(info(`Config: ${REACT_CONFIG_PATH}`));
375
401
  lines.push(info(`Bridge calls: ${stats.bridgeCalls}`));
376
402
  lines.push(info(`Fuzzy matches: ${stats.fuzzyMatches}`));
377
403
  lines.push(info(`Argument normalizations: ${stats.argNormalizations}`));
378
404
  lines.push(info(`Parse failures: ${stats.parseFailures}`));
379
405
  if (reactModeEnabled) {
406
+ registerBridgeTool();
380
407
  lines.push(ok("The tool_call bridge tool is now available to the model"));
381
408
  lines.push(info("ReAct system prompt instructions have been added"));
409
+ lines.push(info("Run /reload to make the bridge tool available to the current model"));
410
+ } else {
411
+ lines.push(warn("The tool_call bridge tool has been unregistered"));
412
+ lines.push(info("Run /reload to remove the tool from the current model"));
382
413
  }
383
414
  const report = lines.join("\n");
384
415
  pi2.sendMessage({