@vtstech/pi-react-fallback 1.0.3
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 +24 -0
- package/react-fallback.js +474 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vtstech/pi-react-fallback",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "ReAct fallback extension for Pi Coding Agent",
|
|
5
|
+
"main": "react-fallback.js",
|
|
6
|
+
"keywords": ["pi-package", "pi-extensions"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"access": "public",
|
|
9
|
+
"author": "VTSTech",
|
|
10
|
+
"homepage": "https://www.vts-tech.org",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/VTSTech/pi-coding-agent"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@vtstech/pi-shared": "1.0.3"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@mariozechner/pi-coding-agent": ">=0.66"
|
|
20
|
+
},
|
|
21
|
+
"pi": {
|
|
22
|
+
"extensions": ["./react-fallback.js"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// .build-npm/react-fallback/react-fallback.temp.ts
|
|
20
|
+
var react_fallback_temp_exports = {};
|
|
21
|
+
__export(react_fallback_temp_exports, {
|
|
22
|
+
default: () => react_fallback_temp_default
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(react_fallback_temp_exports);
|
|
25
|
+
var import_format = require("@vtstech/pi-shared/format");
|
|
26
|
+
function sanitizeModelJson(text) {
|
|
27
|
+
text = text.replace(/:\s*True\b/g, ": true");
|
|
28
|
+
text = text.replace(/:\s*False\b/g, ": false");
|
|
29
|
+
text = text.replace(/:\s*None\b/g, ": null");
|
|
30
|
+
text = text.replace(/\[\s*True\b/g, "[true");
|
|
31
|
+
text = text.replace(/\[\s*False\b/g, "[false");
|
|
32
|
+
text = text.replace(/\[\s*None\b/g, "[null");
|
|
33
|
+
text = text.replace(/("(?:[^"\\]|\\.)*")\s*\+\s*[^,}'"\]\n]+/g, "$1");
|
|
34
|
+
text = text.replace(/,\s*([}\]])/g, "$1");
|
|
35
|
+
text = text.replace(/\\\\\\\\/g, "\\\\");
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
var THOUGHT_RE = /Thought:\s*(.*?)(?=Action:|Final Answer:|$)/is;
|
|
39
|
+
var ACTION_RE = /Action:\s*[`"']?(\w+)[`"']?\s*\n?\s*Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:|Action:)|$)/is;
|
|
40
|
+
var ACTION_RE_SAMELINE = /Action:\s*[`"']?(\w+)[`"']?\s+Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:)|$)/is;
|
|
41
|
+
var ACTION_RE_LOOSE = /Action:\s*(.+?)\n\s*Action Input:\s*(.*?)(?=\n\s*(?:Observation:|Thought:|Final Answer:|Action:)|$)/is;
|
|
42
|
+
var ACTION_RE_PAREN = /Action:\s*(\w+)\s*\(([^)]*)\)/i;
|
|
43
|
+
var FINAL_ANSWER_RE = /Final Answer:\s*([\s\S]*?)$/i;
|
|
44
|
+
function extractJsonArgs(rawArgs) {
|
|
45
|
+
const start = rawArgs.indexOf("{");
|
|
46
|
+
if (start === -1) return null;
|
|
47
|
+
let depth = 0;
|
|
48
|
+
let end = -1;
|
|
49
|
+
for (let i = start; i < rawArgs.length; i++) {
|
|
50
|
+
if (rawArgs[i] === "{") depth++;
|
|
51
|
+
else if (rawArgs[i] === "}") {
|
|
52
|
+
depth--;
|
|
53
|
+
if (depth === 0) {
|
|
54
|
+
end = i;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (end === -1) return null;
|
|
60
|
+
const jsonStr = rawArgs.slice(start, end + 1);
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(jsonStr);
|
|
63
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : { input: String(parsed) };
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const sanitized = sanitizeModelJson(jsonStr);
|
|
68
|
+
const parsed = JSON.parse(sanitized);
|
|
69
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : { input: String(parsed) };
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
const exprMatch = jsonStr.match(/['"]expression['"]:\s*['"]([^'"]+)['"]/);
|
|
73
|
+
if (exprMatch) return { expression: exprMatch[1] };
|
|
74
|
+
const cmdMatch = jsonStr.match(/['"]command['"]:\s*['"]([^'"]+)['"]/);
|
|
75
|
+
if (cmdMatch) return { command: cmdMatch[1] };
|
|
76
|
+
return { input: jsonStr };
|
|
77
|
+
}
|
|
78
|
+
function parseReact(text) {
|
|
79
|
+
let thought;
|
|
80
|
+
const thoughtMatch = THOUGHT_RE.exec(text);
|
|
81
|
+
if (thoughtMatch) thought = thoughtMatch[1].trim();
|
|
82
|
+
let match = ACTION_RE.exec(text);
|
|
83
|
+
if (!match) match = ACTION_RE_SAMELINE.exec(text);
|
|
84
|
+
let looseMatch = false;
|
|
85
|
+
if (!match) match = ACTION_RE_LOOSE.exec(text), looseMatch = true;
|
|
86
|
+
let parenMatch = false;
|
|
87
|
+
if (!match) match = ACTION_RE_PAREN.exec(text), parenMatch = true;
|
|
88
|
+
if (match) {
|
|
89
|
+
let toolName = match[1].trim().replace(/[`"']/g, "");
|
|
90
|
+
if (looseMatch && pi.context?.session?.tools) {
|
|
91
|
+
const availableTools = pi.context.session.tools || [];
|
|
92
|
+
for (const real of availableTools) {
|
|
93
|
+
const rl = real.toLowerCase().replace(/_/g, "");
|
|
94
|
+
if (toolName.toLowerCase().includes(rl)) {
|
|
95
|
+
toolName = real;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (toolName.includes(" ")) {
|
|
100
|
+
const words = toolName.split(/\s+/);
|
|
101
|
+
for (const w of words) {
|
|
102
|
+
const wc = w.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
103
|
+
if (wc.length < 3) continue;
|
|
104
|
+
for (const real of availableTools) {
|
|
105
|
+
const rl = real.toLowerCase().replace(/_/g, "");
|
|
106
|
+
if (rl.includes(wc.toLowerCase())) {
|
|
107
|
+
toolName = real;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!toolName.includes(" ")) break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const rawArgs = match[2].trim().replace(/^```\w*\s*/gm, "").replace(/```\s*$/gm, "").trim();
|
|
116
|
+
let args;
|
|
117
|
+
if (parenMatch && rawArgs && !rawArgs.startsWith("{")) {
|
|
118
|
+
const pairs = rawArgs.match(/(\w+)\s*:\s*("[^"]*"|'[^']*'|\S+)/g);
|
|
119
|
+
if (pairs) {
|
|
120
|
+
const obj = {};
|
|
121
|
+
for (const p of pairs) {
|
|
122
|
+
const colonIdx = p.indexOf(":");
|
|
123
|
+
const key = p.slice(0, colonIdx).trim();
|
|
124
|
+
let val = p.slice(colonIdx + 1).trim();
|
|
125
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
126
|
+
val = val.slice(1, -1);
|
|
127
|
+
}
|
|
128
|
+
obj[key] = val;
|
|
129
|
+
}
|
|
130
|
+
args = obj;
|
|
131
|
+
} else {
|
|
132
|
+
args = { input: rawArgs };
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
args = extractJsonArgs(rawArgs) || { input: rawArgs };
|
|
136
|
+
}
|
|
137
|
+
let finalAnswer;
|
|
138
|
+
const faMatch = FINAL_ANSWER_RE.exec(text);
|
|
139
|
+
if (faMatch) finalAnswer = faMatch[1].trim();
|
|
140
|
+
return { name: toolName, args, thought, finalAnswer, raw: match[0] };
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function extractToolFromJson(obj) {
|
|
145
|
+
if (!obj || typeof obj !== "object") return null;
|
|
146
|
+
let name = obj.name || obj.function || obj.tool || obj.action;
|
|
147
|
+
let args = obj.arguments || obj.parameters || obj.args || obj.actionInput || {};
|
|
148
|
+
if (!name) {
|
|
149
|
+
for (const key of Object.keys(obj)) {
|
|
150
|
+
const kl = key.toLowerCase();
|
|
151
|
+
if (kl === "action" && typeof obj[key] === "string") {
|
|
152
|
+
name = obj[key];
|
|
153
|
+
}
|
|
154
|
+
if (kl === "action input" || kl === "actioninput" || kl === "action_input") {
|
|
155
|
+
const val = obj[key];
|
|
156
|
+
if (typeof val === "object" && val !== null) args = val;
|
|
157
|
+
else if (val) args = { input: val };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!name) {
|
|
162
|
+
const argToTool = { expression: "calculator", command: "shell" };
|
|
163
|
+
const nonToolKeys = /* @__PURE__ */ new Set(["response", "method", "answer", "result", "explanation", "output", "text"]);
|
|
164
|
+
const objKeys = Object.keys(obj);
|
|
165
|
+
if (!objKeys.some((k) => nonToolKeys.has(k))) {
|
|
166
|
+
for (const key of objKeys) {
|
|
167
|
+
if (key in argToTool) {
|
|
168
|
+
name = argToTool[key];
|
|
169
|
+
args = obj;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!name || typeof args !== "object" || args === null) return null;
|
|
176
|
+
return { name, args };
|
|
177
|
+
}
|
|
178
|
+
var WORD_MAPPINGS = {
|
|
179
|
+
calculate: ["calculator"],
|
|
180
|
+
calc: ["calculator"],
|
|
181
|
+
math: ["calculator"],
|
|
182
|
+
compute: ["calculator"],
|
|
183
|
+
eval: ["calculator"],
|
|
184
|
+
expression: ["calculator"],
|
|
185
|
+
power: ["calculator"],
|
|
186
|
+
pow: ["calculator"],
|
|
187
|
+
sqrt: ["calculator"],
|
|
188
|
+
python: ["shell"],
|
|
189
|
+
repl: ["shell"],
|
|
190
|
+
code: ["shell"],
|
|
191
|
+
execute: ["shell"],
|
|
192
|
+
shell: ["bash"],
|
|
193
|
+
bash: ["bash"],
|
|
194
|
+
cmd: ["bash"],
|
|
195
|
+
command: ["bash"],
|
|
196
|
+
ls: ["bash"],
|
|
197
|
+
cat: ["bash"],
|
|
198
|
+
echo: ["bash"],
|
|
199
|
+
grep: ["bash"],
|
|
200
|
+
read: ["read"],
|
|
201
|
+
write: ["write"],
|
|
202
|
+
file: ["read"],
|
|
203
|
+
weather: ["get_weather"],
|
|
204
|
+
search: ["bash"]
|
|
205
|
+
};
|
|
206
|
+
function fuzzyMatchToolName(hallucinated, availableTools) {
|
|
207
|
+
const lower = hallucinated.toLowerCase().replace(/_/g, "");
|
|
208
|
+
if (availableTools.includes(hallucinated)) return hallucinated;
|
|
209
|
+
for (const real of availableTools) {
|
|
210
|
+
const rl = real.toLowerCase().replace(/_/g, "");
|
|
211
|
+
if (rl === lower || rl.includes(lower) || lower.includes(rl)) return real;
|
|
212
|
+
}
|
|
213
|
+
for (const [keyword, hints] of Object.entries(WORD_MAPPINGS)) {
|
|
214
|
+
if (lower.includes(keyword)) {
|
|
215
|
+
for (const hint of hints) {
|
|
216
|
+
for (const real of availableTools) {
|
|
217
|
+
if (real.includes(hint) || real === hint) return real;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (lower.length >= 4) {
|
|
223
|
+
for (const real of availableTools) {
|
|
224
|
+
const rl = real.toLowerCase();
|
|
225
|
+
if (rl.length >= 4 && rl.slice(0, 4) === lower.slice(0, 4)) return real;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
var ARG_ALIASES = {
|
|
231
|
+
expression: ["expr", "exp", "formula", "calculation", "math"],
|
|
232
|
+
file_path: ["path", "filepath", "file", "filename", "location"],
|
|
233
|
+
content: ["text", "data", "body", "value"],
|
|
234
|
+
command: ["cmd", "shell", "script", "exec"],
|
|
235
|
+
url: ["uri", "link", "endpoint", "address"],
|
|
236
|
+
query: ["search", "term", "keywords", "q"],
|
|
237
|
+
input: ["value", "arg", "parameter"],
|
|
238
|
+
timeout: ["time_limit", "max_time", "seconds"]
|
|
239
|
+
};
|
|
240
|
+
function normalizeArguments(args, expectedParams) {
|
|
241
|
+
if (!args || typeof args !== "object") return args;
|
|
242
|
+
const expectedSet = new Set(expectedParams.map((p) => p.toLowerCase()));
|
|
243
|
+
const normalized = {};
|
|
244
|
+
const powerParts = {};
|
|
245
|
+
for (const [key, value] of Object.entries(args)) {
|
|
246
|
+
const keyLower = key.toLowerCase().replace(/-/g, "_");
|
|
247
|
+
let targetParam = null;
|
|
248
|
+
for (const param of expectedParams) {
|
|
249
|
+
if (param.toLowerCase() === keyLower) {
|
|
250
|
+
targetParam = param;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!targetParam) {
|
|
255
|
+
for (const [canonical, aliases] of Object.entries(ARG_ALIASES)) {
|
|
256
|
+
if (aliases.includes(keyLower) && expectedSet.has(canonical.toLowerCase())) {
|
|
257
|
+
targetParam = canonical;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!targetParam) {
|
|
263
|
+
for (const param of expectedParams) {
|
|
264
|
+
if (keyLower.includes(param.toLowerCase()) || keyLower.startsWith(param.toLowerCase())) {
|
|
265
|
+
targetParam = param;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (["base", "value", "x"].includes(keyLower) || ["exponent", "power", "n", "p", "exp"].includes(keyLower)) {
|
|
271
|
+
powerParts[keyLower] = value;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const finalKey = targetParam || key;
|
|
275
|
+
if (!(finalKey in normalized)) normalized[finalKey] = value;
|
|
276
|
+
}
|
|
277
|
+
if (powerParts && expectedSet.has("expression")) {
|
|
278
|
+
const base = powerParts.base ?? powerParts.value ?? powerParts.x;
|
|
279
|
+
const exp = powerParts.exponent ?? powerParts.power ?? powerParts.n ?? powerParts.p ?? powerParts.exp;
|
|
280
|
+
if (base !== void 0 && exp !== void 0) normalized.expression = `${base} ** ${exp}`;
|
|
281
|
+
else if (base !== void 0) normalized.expression = String(base);
|
|
282
|
+
}
|
|
283
|
+
return normalized;
|
|
284
|
+
}
|
|
285
|
+
function looksLikeSchemaDump(text) {
|
|
286
|
+
if (!text) return false;
|
|
287
|
+
const indicators = [
|
|
288
|
+
'{"function <nil>',
|
|
289
|
+
'"type":"function"',
|
|
290
|
+
'"parameters":{"type":"object"',
|
|
291
|
+
'[{"type":',
|
|
292
|
+
'"required":',
|
|
293
|
+
'"properties":'
|
|
294
|
+
];
|
|
295
|
+
const lower = text.toLowerCase();
|
|
296
|
+
const matches = indicators.filter((i) => lower.includes(i.toLowerCase())).length;
|
|
297
|
+
return matches >= 2;
|
|
298
|
+
}
|
|
299
|
+
function react_fallback_temp_default(pi2) {
|
|
300
|
+
let reactModeEnabled = false;
|
|
301
|
+
let stats = { bridgeCalls: 0, fuzzyMatches: 0, argNormalizations: 0, parseFailures: 0 };
|
|
302
|
+
const branding = [
|
|
303
|
+
` \u26A1 Pi ReAct Fallback Extension v1.0.3`,
|
|
304
|
+
` Written by VTSTech`,
|
|
305
|
+
` GitHub: https://github.com/VTSTech`,
|
|
306
|
+
` Website: www.vts-tech.org`
|
|
307
|
+
].join("\n");
|
|
308
|
+
pi2.registerTool({
|
|
309
|
+
name: "tool_call",
|
|
310
|
+
label: "Universal Tool Call",
|
|
311
|
+
description: `Universal tool call bridge. Use this to call any available tool by specifying its name and arguments as JSON.
|
|
312
|
+
|
|
313
|
+
To use: call tool_call with:
|
|
314
|
+
- name: the exact tool name (e.g. "bash", "read", "write", "edit")
|
|
315
|
+
- arguments: a JSON string of the tool's arguments (e.g. '{"command": "ls -la"}')
|
|
316
|
+
|
|
317
|
+
The bridge will match your tool name (fuzzy matching supported) and normalize argument names automatically.`,
|
|
318
|
+
promptSnippet: "tool_call - universal bridge for calling any tool",
|
|
319
|
+
promptGuidelines: [
|
|
320
|
+
"When you need to use a tool but are unsure of the exact name, use tool_call with the tool name and arguments.",
|
|
321
|
+
`Example: tool_call(name='bash', arguments='{"command": "ls -la"}')`
|
|
322
|
+
],
|
|
323
|
+
parameters: {
|
|
324
|
+
type: "object",
|
|
325
|
+
properties: {
|
|
326
|
+
name: { type: "string", description: "Name of the tool to call (fuzzy matching supported)" },
|
|
327
|
+
arguments: { type: "string", description: "Tool arguments as a JSON object string" }
|
|
328
|
+
},
|
|
329
|
+
required: ["name", "arguments"]
|
|
330
|
+
},
|
|
331
|
+
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
332
|
+
const p = params;
|
|
333
|
+
const requestedName = p.name || "";
|
|
334
|
+
const argsStr = p.arguments || "{}";
|
|
335
|
+
stats.bridgeCalls++;
|
|
336
|
+
let args;
|
|
337
|
+
try {
|
|
338
|
+
args = JSON.parse(argsStr);
|
|
339
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
340
|
+
args = { input: argsStr };
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
args = { input: argsStr };
|
|
344
|
+
}
|
|
345
|
+
const allTools = pi2.getAllTools();
|
|
346
|
+
let targetToolName = null;
|
|
347
|
+
if (allTools.includes(requestedName)) {
|
|
348
|
+
targetToolName = requestedName;
|
|
349
|
+
} else {
|
|
350
|
+
targetToolName = fuzzyMatchToolName(requestedName, allTools);
|
|
351
|
+
if (targetToolName) stats.fuzzyMatches++;
|
|
352
|
+
}
|
|
353
|
+
if (!targetToolName) {
|
|
354
|
+
stats.parseFailures++;
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: `Error: Unknown tool "${requestedName}". Available tools: ${allTools.join(", ")}` }],
|
|
357
|
+
isError: true
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const normalizedArgs = Object.keys(args).length > 0 ? args : {};
|
|
361
|
+
stats.argNormalizations++;
|
|
362
|
+
const argsJson = JSON.stringify(normalizedArgs);
|
|
363
|
+
return {
|
|
364
|
+
content: [{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: `[ReAct Bridge] Tool resolved: ${requestedName} \u2192 ${targetToolName}${targetToolName !== requestedName ? " (fuzzy matched)" : ""}
|
|
367
|
+
|
|
368
|
+
Please call ${targetToolName} with these arguments:
|
|
369
|
+
${argsJson}`
|
|
370
|
+
}],
|
|
371
|
+
isError: false
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
pi2.on("context", (event) => {
|
|
376
|
+
if (!reactModeEnabled) return;
|
|
377
|
+
const model = event.messages;
|
|
378
|
+
for (let i = model.length - 1; i >= 0; i--) {
|
|
379
|
+
const msg = model[i];
|
|
380
|
+
if (msg && msg.role === "system") {
|
|
381
|
+
const content = msg.content || "";
|
|
382
|
+
if (!content.includes("[ReAct Fallback Mode]")) {
|
|
383
|
+
msg.content = content + '\n\n[ReAct Fallback Mode]\nYou have access to tools via the `tool_call` bridge tool.\nTo call a tool, use: tool_call(name="<tool_name>", arguments="<json_args>")\nAvailable tools will be listed in your tool definitions.\nAlways use tool_call to interact with files, run commands, or perform calculations.';
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
pi2.registerCommand("react-mode", {
|
|
390
|
+
description: "Toggle ReAct fallback mode for models without native tool calling",
|
|
391
|
+
handler: async (_args, ctx) => {
|
|
392
|
+
reactModeEnabled = !reactModeEnabled;
|
|
393
|
+
const status = reactModeEnabled ? "ENABLED" : "DISABLED";
|
|
394
|
+
ctx.ui.notify(`ReAct mode ${status}`, "success");
|
|
395
|
+
const lines = [branding];
|
|
396
|
+
lines.push((0, import_format.section)("REACT FALLBACK MODE"));
|
|
397
|
+
lines.push((0, import_format.info)(`Status: ${status}`));
|
|
398
|
+
lines.push((0, import_format.info)(`Bridge calls: ${stats.bridgeCalls}`));
|
|
399
|
+
lines.push((0, import_format.info)(`Fuzzy matches: ${stats.fuzzyMatches}`));
|
|
400
|
+
lines.push((0, import_format.info)(`Argument normalizations: ${stats.argNormalizations}`));
|
|
401
|
+
lines.push((0, import_format.info)(`Parse failures: ${stats.parseFailures}`));
|
|
402
|
+
if (reactModeEnabled) {
|
|
403
|
+
lines.push((0, import_format.ok)("The tool_call bridge tool is now available to the model"));
|
|
404
|
+
lines.push((0, import_format.info)("ReAct system prompt instructions have been added"));
|
|
405
|
+
}
|
|
406
|
+
const report = lines.join("\n");
|
|
407
|
+
pi2.sendMessage({
|
|
408
|
+
customType: "react-mode-report",
|
|
409
|
+
content: report,
|
|
410
|
+
display: { type: "content", content: report }
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
pi2.registerCommand("react-parse", {
|
|
415
|
+
description: "Test the ReAct parser against a text input: /react-parse <text>",
|
|
416
|
+
handler: async (args, ctx) => {
|
|
417
|
+
const text = args.trim();
|
|
418
|
+
if (!text) {
|
|
419
|
+
ctx.ui.notify("Provide text to parse: /react-parse <text>", "error");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const lines = [branding];
|
|
423
|
+
lines.push((0, import_format.section)("REACT PARSER TEST"));
|
|
424
|
+
lines.push((0, import_format.info)(`Input: ${text.slice(0, 100)}${text.length > 100 ? "..." : ""}`));
|
|
425
|
+
const reactResult = parseReact(text);
|
|
426
|
+
if (reactResult) {
|
|
427
|
+
lines.push((0, import_format.ok)("ReAct format detected!"));
|
|
428
|
+
lines.push((0, import_format.info)(`Tool: ${reactResult.name}`));
|
|
429
|
+
lines.push((0, import_format.info)(`Args: ${JSON.stringify(reactResult.args)}`));
|
|
430
|
+
if (reactResult.thought) lines.push((0, import_format.info)(`Thought: ${reactResult.thought}`));
|
|
431
|
+
if (reactResult.finalAnswer) lines.push((0, import_format.info)(`Final Answer: ${reactResult.finalAnswer}`));
|
|
432
|
+
} else {
|
|
433
|
+
lines.push((0, import_format.fail)("No ReAct format detected"));
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
const firstBrace = text.indexOf("{");
|
|
437
|
+
if (firstBrace !== -1) {
|
|
438
|
+
const lastBrace = text.lastIndexOf("}");
|
|
439
|
+
if (lastBrace > firstBrace) {
|
|
440
|
+
const jsonStr = text.slice(firstBrace, lastBrace + 1);
|
|
441
|
+
const parsed = JSON.parse(sanitizeModelJson(jsonStr));
|
|
442
|
+
const toolResult = extractToolFromJson(parsed);
|
|
443
|
+
if (toolResult) {
|
|
444
|
+
lines.push((0, import_format.ok)("JSON tool call detected!"));
|
|
445
|
+
lines.push((0, import_format.info)(`Tool: ${toolResult.name}`));
|
|
446
|
+
lines.push((0, import_format.info)(`Args: ${JSON.stringify(toolResult.args)}`));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
if (looksLikeSchemaDump(text)) {
|
|
453
|
+
lines.push((0, import_format.warn)("Text appears to be a tool schema dump (not a tool call)"));
|
|
454
|
+
}
|
|
455
|
+
if (FINAL_ANSWER_RE.test(text)) {
|
|
456
|
+
const fa = FINAL_ANSWER_RE.exec(text)[1].trim();
|
|
457
|
+
lines.push((0, import_format.ok)(`Final Answer: ${fa}`));
|
|
458
|
+
}
|
|
459
|
+
pi2.sendMessage({
|
|
460
|
+
customType: "react-parse-report",
|
|
461
|
+
content: lines.join("\n"),
|
|
462
|
+
display: { type: "content", content: lines.join("\n") }
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
pi2._reactParser = {
|
|
467
|
+
parseReact,
|
|
468
|
+
sanitizeModelJson,
|
|
469
|
+
extractToolFromJson,
|
|
470
|
+
fuzzyMatchToolName,
|
|
471
|
+
normalizeArguments,
|
|
472
|
+
looksLikeSchemaDump
|
|
473
|
+
};
|
|
474
|
+
}
|