@vtstech/pi-react-fallback 1.0.8 → 1.0.9

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