File size: 1,936 Bytes
5e4b249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { logger } from './logger.js';

/**
 * Extract code blocks from markdown AI responses.
 */
export function extractCodeBlocks(markdown: string): Array<{ language: string; code: string }> {
  const regex = /```(\w*)\n([\s\S]*?)```/g;
  const blocks: Array<{ language: string; code: string }> = [];
  let match;
  while ((match = regex.exec(markdown)) !== null) {
    blocks.push({ language: match[1] || 'plaintext', code: match[2].trim() });
  }
  return blocks;
}

/**
 * Parse JSON from potentially malformed AI responses.
 * Attempts multiple strategies: direct parse, markdown extraction, brace matching.
 */
export function parseJSONSafe<T>(raw: string, fallback: T): T {
  // Strategy 1: Direct parse
  try {
    return JSON.parse(raw) as T;
  } catch {
    // continue
  }

  // Strategy 2: Extract from markdown code fence
  const jsonFence = raw.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
  if (jsonFence) {
    try {
      return JSON.parse(jsonFence[1].trim()) as T;
    } catch {
      // continue
    }
  }

  // Strategy 3: Find first { and last }
  const firstBrace = raw.indexOf('{');
  const lastBrace = raw.lastIndexOf('}');
  if (firstBrace !== -1 && lastBrace > firstBrace) {
    try {
      return JSON.parse(raw.substring(firstBrace, lastBrace + 1)) as T;
    } catch {
      // continue
    }
  }

  // Strategy 4: Find array brackets
  const firstBracket = raw.indexOf('[');
  const lastBracket = raw.lastIndexOf(']');
  if (firstBracket !== -1 && lastBracket > firstBracket) {
    try {
      return JSON.parse(raw.substring(firstBracket, lastBracket + 1)) as T;
    } catch {
      // continue
    }
  }

  logger.warn('Failed to parse JSON from AI response, using fallback');
  return fallback;
}

/**
 * Strip markdown code fences from a string (for inline completions).
 */
export function stripCodeFences(text: string): string {
  return text.replace(/^```\w*\n?/, '').replace(/\n?```$/, '').trim();
}