node-red-contrib-ai-agent 0.5.10 → 0.5.11

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.
@@ -148,7 +148,13 @@ Return a JSON object with a "tasks" array. Each task should have:
148
148
  prompt += `\n\nThink about parallel execution. Group related tasks and identify bottlenecks. Ensure dependencies are logical.`;
149
149
  }
150
150
 
151
- prompt += `\n\nExample:
151
+ prompt += `\n\nIMPORTANT OUTPUT RULES:
152
+ - Return ONLY raw JSON (no markdown, no code fences, no explanations)
153
+ - Do NOT include comments (e.g. // ...)
154
+ - Do NOT include trailing commas
155
+ - All string values must be valid JSON strings (escape newlines as \\n if needed)
156
+
157
+ Example:
152
158
  {
153
159
  "tasks": [
154
160
  {"id": "t1", "type": "research", "input": "...", "status": "pending", "priority": 10, "dependsOn": []},
@@ -160,7 +166,7 @@ Return a JSON object with a "tasks" array. Each task should have:
160
166
  node.warn("Prompt: " + prompt);
161
167
  const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates non-linear plans with dependencies.");
162
168
  node.warn("Response: " + response);
163
- const planData = JSON.parse(extractJson(response));
169
+ const planData = parseJsonResponse(response);
164
170
  msg.orchestration.plan = planData;
165
171
  msg.orchestration.status = 'executing';
166
172
  } catch (error) {
@@ -218,7 +224,7 @@ Return a JSON object:
218
224
 
219
225
  try {
220
226
  const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress and manages plan revisions.");
221
- const reflection = JSON.parse(extractJson(response));
227
+ const reflection = parseJsonResponse(response);
222
228
 
223
229
  msg.orchestration.status = reflection.status;
224
230
  if (reflection.updatedPlan) {
@@ -302,5 +308,107 @@ Return a JSON object:
302
308
  return match ? match[0] : text;
303
309
  }
304
310
 
311
+ /**
312
+ * Parses a JSON object from an AI response, tolerating common non-JSON wrappers.
313
+ * @param {string} text - The AI response
314
+ * @returns {any} Parsed JSON
315
+ */
316
+ function parseJsonResponse(text) {
317
+ const extracted = extractJson(text);
318
+ try {
319
+ return JSON.parse(extracted);
320
+ } catch (_err) {
321
+ const sanitized = sanitizeJsonLikeText(extracted);
322
+ return JSON.parse(sanitized);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Removes markdown fences and JS-style comments, and escapes raw newlines inside string literals.
328
+ * This is a best-effort repair for model outputs that are "almost JSON".
329
+ * @param {string} input - A string that should contain a JSON object
330
+ * @returns {string} A JSON string more likely to be parseable by JSON.parse
331
+ */
332
+ function sanitizeJsonLikeText(input) {
333
+ if (typeof input !== 'string') return '';
334
+
335
+ // Remove common markdown code fences
336
+ let s = input
337
+ .replace(/^\s*```(?:json)?\s*/i, '')
338
+ .replace(/\s*```\s*$/i, '')
339
+ .trim();
340
+
341
+ // If we still have leading/trailing non-JSON, re-extract
342
+ s = extractJson(s).trim();
343
+
344
+ let out = '';
345
+ let inString = false;
346
+ let escape = false;
347
+ let inLineComment = false;
348
+
349
+ for (let i = 0; i < s.length; i++) {
350
+ const ch = s[i];
351
+ const next = i + 1 < s.length ? s[i + 1] : '';
352
+
353
+ if (inLineComment) {
354
+ if (ch === '\n') {
355
+ inLineComment = false;
356
+ out += ch;
357
+ }
358
+ continue;
359
+ }
360
+
361
+ if (!inString && ch === '/' && next === '/') {
362
+ inLineComment = true;
363
+ i++;
364
+ continue;
365
+ }
366
+
367
+ if (!inString && ch === '`') {
368
+ // ignore stray backticks
369
+ continue;
370
+ }
371
+
372
+ if (inString) {
373
+ if (escape) {
374
+ out += ch;
375
+ escape = false;
376
+ continue;
377
+ }
378
+ if (ch === '\\') {
379
+ out += ch;
380
+ escape = true;
381
+ continue;
382
+ }
383
+ if (ch === '"') {
384
+ out += ch;
385
+ inString = false;
386
+ continue;
387
+ }
388
+ if (ch === '\n') {
389
+ out += '\\n';
390
+ continue;
391
+ }
392
+ if (ch === '\r') {
393
+ // drop CR; newline will be handled by \n
394
+ continue;
395
+ }
396
+ out += ch;
397
+ continue;
398
+ }
399
+
400
+ if (ch === '"') {
401
+ out += ch;
402
+ inString = true;
403
+ escape = false;
404
+ continue;
405
+ }
406
+
407
+ out += ch;
408
+ }
409
+
410
+ return out.trim();
411
+ }
412
+
305
413
  RED.nodes.registerType('ai-orchestrator', AiOrchestratorNode);
306
414
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ai-agent",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "AI Agent for Node-RED",
5
5
  "repository": {
6
6
  "type": "git",