Skip to content

Commit 96506f2

Browse files
committed
Improve JSON parsing robustness and script language enforcement
1 parent 8184dd0 commit 96506f2

2 files changed

Lines changed: 65 additions & 24 deletions

File tree

src/App.jsx

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,45 @@ function App() {
160160
return selectedModels[defaultProvider] || availableModels[defaultProvider]?.[0]?.id || "default";
161161
};
162162

163+
const extractJsonObject = (raw) => {
164+
if (!raw || typeof raw !== "string") return null;
165+
166+
let text = raw.trim();
167+
168+
// Remove markdown fences if the model returns ```json ... ```
169+
text = text.replace(/^```(?:json)?\s*/i, "").replace(/```$/i, "").trim();
170+
171+
const first = text.indexOf("{");
172+
const last = text.lastIndexOf("}");
173+
if (first === -1 || last === -1 || last <= first) return null;
174+
175+
let candidate = text.slice(first, last + 1);
176+
177+
const tryParse = (value) => {
178+
try {
179+
return JSON.parse(value);
180+
} catch {
181+
return null;
182+
}
183+
};
184+
185+
let parsed = tryParse(candidate);
186+
if (parsed) return parsed;
187+
188+
// Common LLM issue on Windows paths: C:\Users\... creates invalid JSON escapes.
189+
// Escape only backslashes that are not valid JSON escape sequences.
190+
const escapedBackslashes = candidate.replace(/\\(?!["\\/bfnrtu])/g, "\\\\");
191+
parsed = tryParse(escapedBackslashes);
192+
if (parsed) return parsed;
193+
194+
// Common LLM issue: raw control characters inside strings.
195+
const cleanedControls = escapedBackslashes.replace(/[\u0000-\u001F]+/g, " ");
196+
parsed = tryParse(cleanedControls);
197+
if (parsed) return parsed;
198+
199+
return null;
200+
};
201+
163202
// ============================================================
164203
// HANDLERS CON HISTORY
165204
// ============================================================
@@ -179,9 +218,8 @@ function App() {
179218
const model = getCurrentModel();
180219
const response = await callAI(defaultProvider, apiKey, prompt, model);
181220

182-
const jsonMatch = response.match(/\{[\s\S]*\}/);
183-
if (jsonMatch) {
184-
const result = JSON.parse(jsonMatch[0]);
221+
const result = extractJsonObject(response);
222+
if (result) {
185223
showToast("Analisi completata!", "success");
186224
history.addEntry({
187225
tool: 'logAnalyzer',
@@ -215,9 +253,8 @@ function App() {
215253
const model = getCurrentModel();
216254
const response = await callAI(defaultProvider, apiKey, prompt, model);
217255

218-
const jsonMatch = response.match(/\{[\s\S]*\}/);
219-
if (jsonMatch) {
220-
const result = JSON.parse(jsonMatch[0]);
256+
const result = extractJsonObject(response);
257+
if (result) {
221258
showToast("Comando generato!", "success");
222259
history.addEntry({
223260
tool: 'commandCrafter',
@@ -251,9 +288,8 @@ function App() {
251288
const model = getCurrentModel();
252289
const response = await callAI(defaultProvider, apiKey, prompt, model);
253290

254-
const jsonMatch = response.match(/\{[\s\S]*\}/);
255-
if (jsonMatch) {
256-
const result = JSON.parse(jsonMatch[0]);
291+
const result = extractJsonObject(response);
292+
if (result) {
257293
showToast("Spiegazione completata!", "success");
258294
history.addEntry({
259295
tool: 'explainMode',
@@ -287,9 +323,8 @@ function App() {
287323
const model = getCurrentModel();
288324
const response = await callAI(defaultProvider, apiKey, prompt, model);
289325

290-
const jsonMatch = response.match(/\{[\s\S]*\}/);
291-
if (jsonMatch) {
292-
const result = JSON.parse(jsonMatch[0]);
326+
const result = extractJsonObject(response);
327+
if (result) {
293328
showToast("Configurazione generata!", "success");
294329
history.addEntry({
295330
tool: 'configGenerator',
@@ -323,9 +358,8 @@ function App() {
323358
const model = getCurrentModel();
324359
const response = await callAI(defaultProvider, apiKey, prompt, model);
325360

326-
const jsonMatch = response.match(/\{[\s\S]*\}/);
327-
if (jsonMatch) {
328-
const result = JSON.parse(jsonMatch[0]);
361+
const result = extractJsonObject(response);
362+
if (result) {
329363
showToast("Diagnosi completata!", "success");
330364
history.addEntry({
331365
tool: 'troubleshooter',
@@ -389,7 +423,7 @@ function App() {
389423
const end = response.lastIndexOf('}');
390424
if (start !== -1 && end !== -1 && end > start) {
391425
try {
392-
const parsed = JSON.parse(response.substring(start, end + 1));
426+
const parsed = extractJsonObject(response.substring(start, end + 1));
393427
if (parsed.script) {
394428
result = { ...result, ...parsed };
395429
showToast("Script generato!", "success");
@@ -438,9 +472,8 @@ function App() {
438472
const model = getCurrentModel();
439473
const response = await callAI(defaultProvider, apiKey, prompt, model);
440474

441-
const jsonMatch = response.match(/\{[\s\S]*\}/);
442-
if (jsonMatch) {
443-
const result = JSON.parse(jsonMatch[0]);
475+
const result = extractJsonObject(response);
476+
if (result) {
444477
showToast("Analisi sicurezza completata!", "success");
445478
history.addEntry({
446479
tool: 'securityAuditor',
@@ -474,9 +507,8 @@ function App() {
474507
const model = getCurrentModel();
475508
const response = await callAI(defaultProvider, apiKey, prompt, model);
476509

477-
const jsonMatch = response.match(/\{[\s\S]*\}/);
478-
if (jsonMatch) {
479-
const result = JSON.parse(jsonMatch[0]);
510+
const result = extractJsonObject(response);
511+
if (result) {
480512
showToast("Analisi scan completata!", "success");
481513
history.addEntry({
482514
tool: 'securityAuditor',

src/utils/aiProviders.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,21 @@ ${getProfessionalOutputContract('Script Builder')}
536536
TASK: Generate a complete, production-ready script.
537537
538538
SCRIPT TYPE: ${scriptType || 'bash'}
539+
540+
CRITICAL LANGUAGE RULE:
541+
- You MUST generate the script ONLY in the requested SCRIPT TYPE.
542+
- If SCRIPT TYPE is python, generate Python only. Do not generate Bash.
543+
- If SCRIPT TYPE is powershell, generate PowerShell only. Do not generate Bash.
544+
- If SCRIPT TYPE is bash, generate Bash only.
545+
- The filename extension, syntax, comments, dependencies and usage MUST match the requested SCRIPT TYPE.
546+
- Do not wrap the script inside another language.
547+
539548
REQUIREMENTS:
540549
${description}
541550
542551
The script MUST include:
543-
- Shebang line
544-
- Strict mode where appropriate, e.g. set -euo pipefail for bash
552+
- A shebang line ONLY when appropriate for the requested script type
553+
- Strict mode where appropriate, e.g. set -euo pipefail for bash, but do not add Bash-specific strict mode to Python or PowerShell
545554
- Input validation
546555
- Logging
547556
- Helpful comments

0 commit comments

Comments
 (0)