@vtstech/pi-react-fallback 1.0.8 → 1.1.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/react-fallback.js +132 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtstech/pi-react-fallback",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
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.8"
17
+ "@vtstech/pi-shared": "1.1.0"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@mariozechner/pi-coding-agent": ">=0.66"
package/react-fallback.js CHANGED
@@ -3,6 +3,7 @@ import os from "node:os";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { section, ok, fail, warn, info } from "@vtstech/pi-shared/format";
6
+ import { EXTENSION_VERSION } from "@vtstech/pi-shared/ollama";
6
7
  function sanitizeModelJson(text) {
7
8
  text = text.replace(/:\s*True\b/g, ": true");
8
9
  text = text.replace(/:\s*False\b/g, ": false");
@@ -15,12 +16,72 @@ function sanitizeModelJson(text) {
15
16
  text = text.replace(/\\\\\\\\/g, "\\\\");
16
17
  return text;
17
18
  }
18
- var THOUGHT_RE = /Thought:\s*(.*?)(?=Action:|Final Answer:|$)/is;
19
- var ACTION_RE = /Action:\s*[`"']?(\w+)[`"']?\s*\n?\s*Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:|Action:)|$)/is;
20
- var ACTION_RE_SAMELINE = /Action:\s*[`"']?(\w+)[`"']?\s+Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:)|$)/is;
21
- var ACTION_RE_LOOSE = /Action:\s*(.+?)\n\s*Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:|Action:)|$)/is;
22
- var ACTION_RE_PAREN = /Action:\s*(\w+)\s*\(([^)]*)\)/i;
23
- var FINAL_ANSWER_RE = /Final Answer:\s*([\s\S]*?)$/i;
19
+ var REACT_DIALECTS = [
20
+ {
21
+ name: "react",
22
+ actionTag: "Action:",
23
+ inputTag: "Action Input:",
24
+ thoughtTag: "Thought:",
25
+ stopTags: ["Observation:", "Thought:", "Final Answer:", "Action:"],
26
+ finalTag: "Final Answer:"
27
+ },
28
+ {
29
+ name: "function",
30
+ actionTag: "Function:",
31
+ inputTag: "Function Input:",
32
+ thoughtTag: "Thought:",
33
+ stopTags: ["Observation:", "Thought:", "Final Answer:", "Function:", "Action:"],
34
+ finalTag: "Final Answer:"
35
+ },
36
+ {
37
+ name: "tool",
38
+ actionTag: "Tool:",
39
+ inputTag: "Tool Input:",
40
+ thoughtTag: "Thought:",
41
+ stopTags: ["Observation:", "Thought:", "Final Answer:", "Tool:", "Action:"],
42
+ finalTag: "Final Answer:"
43
+ },
44
+ {
45
+ name: "call",
46
+ actionTag: "Call:",
47
+ inputTag: "Input:",
48
+ thoughtTag: "Thought:",
49
+ stopTags: ["Observation:", "Thought:", "Final Answer:", "Call:", "Action:"],
50
+ finalTag: "Final Answer:"
51
+ }
52
+ ];
53
+ function buildDialectPatterns(d) {
54
+ const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
55
+ const aT = esc(d.actionTag);
56
+ const iT = esc(d.inputTag);
57
+ const stopAlt = d.stopTags.map(esc).join("|");
58
+ const tT = d.thoughtTag ? esc(d.thoughtTag) : void 0;
59
+ const fT = d.finalTag ? esc(d.finalTag) : void 0;
60
+ const thoughtRe = tT ? new RegExp(`${tT}\\s*(.*?)(?=${aT}|${fT}|$)`, "is") : void 0;
61
+ const actionRe = new RegExp(
62
+ `${aT}\\s*[\\x60"']?(\\w+)[\\x60"']?\\s*\\n?\\s*${iT}\\s*(.*?)(?=\\n\\s*(?:${stopAlt})|$)`,
63
+ "is"
64
+ );
65
+ const actionReSameline = new RegExp(
66
+ `${aT}\\s*[\\x60"']?(\\w+)[\\x60"']?\\s+${iT}\\s*(.*?)(?=\\n\\s*(?:${stopAlt})|$)`,
67
+ "is"
68
+ );
69
+ const actionReLoose = new RegExp(
70
+ `${aT}\\s*(.+?)\\n\\s*${iT}\\s*(.*?)(?=\\n\\s*(?:${stopAlt})|$)`,
71
+ "is"
72
+ );
73
+ const actionReParen = new RegExp(`${aT}\\s*(\\w+)\\s*\\(([^)]*)\\)`, "i");
74
+ const finalAnswerRe = fT ? new RegExp(`${fT}\\s*([\\s\\S]*?)$`, "i") : void 0;
75
+ return { thoughtRe, actionRe, actionReSameline, actionReLoose, actionReParen, finalAnswerRe, dialect: d };
76
+ }
77
+ var ALL_DIALECT_PATTERNS = REACT_DIALECTS.map(buildDialectPatterns);
78
+ var CLASSIC_PATTERNS = ALL_DIALECT_PATTERNS[0];
79
+ var THOUGHT_RE = CLASSIC_PATTERNS.thoughtRe;
80
+ var ACTION_RE = CLASSIC_PATTERNS.actionRe;
81
+ var ACTION_RE_SAMELINE = CLASSIC_PATTERNS.actionReSameline;
82
+ var ACTION_RE_LOOSE = CLASSIC_PATTERNS.actionReLoose;
83
+ var ACTION_RE_PAREN = CLASSIC_PATTERNS.actionReParen;
84
+ var FINAL_ANSWER_RE = CLASSIC_PATTERNS.finalAnswerRe;
24
85
  function extractJsonArgs(rawArgs) {
25
86
  const start = rawArgs.indexOf("{");
26
87
  if (start === -1) return null;
@@ -56,18 +117,43 @@ function extractJsonArgs(rawArgs) {
56
117
  return { input: jsonStr };
57
118
  }
58
119
  function parseReact(text) {
120
+ for (const dp of ALL_DIALECT_PATTERNS) {
121
+ const result = parseReactWithPatterns(text, dp);
122
+ if (result) return result;
123
+ }
124
+ return null;
125
+ }
126
+ function parseReactWithPatterns(text, dp, tightLoose = false) {
59
127
  let thought;
60
- const thoughtMatch = THOUGHT_RE.exec(text);
61
- if (thoughtMatch) thought = thoughtMatch[1].trim();
62
- let match = ACTION_RE.exec(text);
63
- if (!match) match = ACTION_RE_SAMELINE.exec(text);
128
+ if (dp.thoughtRe) {
129
+ const thoughtMatch = dp.thoughtRe.exec(text);
130
+ if (thoughtMatch) thought = thoughtMatch[1].trim();
131
+ }
132
+ let match = dp.actionRe.exec(text);
133
+ if (!match) match = dp.actionReSameline.exec(text);
64
134
  let looseMatch = false;
65
- if (!match) match = ACTION_RE_LOOSE.exec(text), looseMatch = true;
135
+ if (!match) {
136
+ const looseResult = dp.actionReLoose.exec(text);
137
+ if (looseResult) {
138
+ if (tightLoose) {
139
+ const candidate = looseResult[1].trim().replace(/[`"']/g, "");
140
+ const isToolIdentifier = /^\w+$/.test(candidate) && (candidate.includes("_") || candidate.includes("-"));
141
+ const isKnownTool = /^(get_weather|calculate)$/i.test(candidate);
142
+ if (isToolIdentifier || isKnownTool) {
143
+ match = looseResult;
144
+ looseMatch = true;
145
+ }
146
+ } else {
147
+ match = looseResult;
148
+ looseMatch = true;
149
+ }
150
+ }
151
+ }
66
152
  let parenMatch = false;
67
- if (!match) match = ACTION_RE_PAREN.exec(text), parenMatch = true;
153
+ if (!match) match = dp.actionReParen.exec(text), parenMatch = true;
68
154
  if (match) {
69
155
  let toolName = match[1].trim().replace(/[`"']/g, "");
70
- if (looseMatch && pi.context?.session?.tools) {
156
+ if (looseMatch && !tightLoose && pi.context?.session?.tools) {
71
157
  const availableTools = pi.context.session.tools || [];
72
158
  for (const real of availableTools) {
73
159
  const rl = real.toLowerCase().replace(/_/g, "");
@@ -115,9 +201,18 @@ function parseReact(text) {
115
201
  args = extractJsonArgs(rawArgs) || { input: rawArgs };
116
202
  }
117
203
  let finalAnswer;
118
- const faMatch = FINAL_ANSWER_RE.exec(text);
119
- if (faMatch) finalAnswer = faMatch[1].trim();
120
- return { name: toolName, args, thought, finalAnswer, raw: match[0] };
204
+ if (dp.finalAnswerRe) {
205
+ const faMatch = dp.finalAnswerRe.exec(text);
206
+ if (faMatch) finalAnswer = faMatch[1].trim();
207
+ }
208
+ return { name: toolName, args, thought, finalAnswer, raw: match[0], dialect: dp.dialect.name };
209
+ }
210
+ return null;
211
+ }
212
+ function detectReactDialect(text) {
213
+ for (const dp of ALL_DIALECT_PATTERNS) {
214
+ const tagPattern = new RegExp(`^\\s*${dp.dialect.actionTag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*`, "im");
215
+ if (tagPattern.test(text)) return dp.dialect;
121
216
  }
122
217
  return null;
123
218
  }
@@ -296,7 +391,7 @@ function react_fallback_temp_default(pi2) {
296
391
  let reactModeEnabled = readReactConfig().enabled;
297
392
  let stats = { bridgeCalls: 0, fuzzyMatches: 0, argNormalizations: 0, parseFailures: 0 };
298
393
  const branding = [
299
- ` \u26A1 Pi ReAct Fallback Extension v1.0.8`,
394
+ ` \u26A1 Pi ReAct Fallback Extension v${EXTENSION_VERSION}`,
300
395
  ` Written by VTSTech`,
301
396
  ` GitHub: https://github.com/VTSTech`,
302
397
  ` Website: www.vts-tech.org`
@@ -430,15 +525,23 @@ ${argsJson}`
430
525
  const lines = [branding];
431
526
  lines.push(section("REACT PARSER TEST"));
432
527
  lines.push(info(`Input: ${text.slice(0, 100)}${text.length > 100 ? "..." : ""}`));
528
+ const detectedDialect = detectReactDialect(text);
433
529
  const reactResult = parseReact(text);
434
530
  if (reactResult) {
435
- lines.push(ok("ReAct format detected!"));
531
+ lines.push(ok(`ReAct format detected! (dialect: ${reactResult.dialect || "react"})`));
436
532
  lines.push(info(`Tool: ${reactResult.name}`));
437
533
  lines.push(info(`Args: ${JSON.stringify(reactResult.args)}`));
438
534
  if (reactResult.thought) lines.push(info(`Thought: ${reactResult.thought}`));
439
535
  if (reactResult.finalAnswer) lines.push(info(`Final Answer: ${reactResult.finalAnswer}`));
440
536
  } else {
441
- lines.push(fail("No ReAct format detected"));
537
+ if (detectedDialect) {
538
+ lines.push(warn(`Dialect tag "${detectedDialect.actionTag}" detected but no valid tool call parsed`));
539
+ } else {
540
+ lines.push(fail("No ReAct format detected"));
541
+ }
542
+ }
543
+ if (detectedDialect && detectedDialect.name !== "react") {
544
+ lines.push(info(`Detected dialect: ${detectedDialect.name} (${detectedDialect.actionTag} / ${detectedDialect.inputTag})`));
442
545
  }
443
546
  try {
444
547
  const firstBrace = text.indexOf("{");
@@ -460,8 +563,9 @@ ${argsJson}`
460
563
  if (looksLikeSchemaDump(text)) {
461
564
  lines.push(warn("Text appears to be a tool schema dump (not a tool call)"));
462
565
  }
463
- if (FINAL_ANSWER_RE.test(text)) {
464
- const fa = FINAL_ANSWER_RE.exec(text)[1].trim();
566
+ const faMatch = FINAL_ANSWER_RE.exec(text);
567
+ if (faMatch) {
568
+ const fa = faMatch[1].trim();
465
569
  lines.push(ok(`Final Answer: ${fa}`));
466
570
  }
467
571
  pi2.sendMessage({
@@ -473,13 +577,18 @@ ${argsJson}`
473
577
  });
474
578
  pi2._reactParser = {
475
579
  parseReact,
580
+ parseReactWithPatterns,
581
+ detectReactDialect,
476
582
  sanitizeModelJson,
477
583
  extractToolFromJson,
478
584
  fuzzyMatchToolName,
479
585
  normalizeArguments,
480
- looksLikeSchemaDump
586
+ looksLikeSchemaDump,
587
+ REACT_DIALECTS,
588
+ ALL_DIALECT_PATTERNS
481
589
  };
482
590
  }
483
591
  export {
484
- react_fallback_temp_default as default
592
+ react_fallback_temp_default as default,
593
+ detectReactDialect
485
594
  };