@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.
- package/package.json +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
|
|
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
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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)
|
|
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 =
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
};
|