tool/Analysis/functions/Repair-JsonResponse.ps1

function Repair-JsonResponse {
    <#
    .SYNOPSIS
        Repairs common LLM JSON response issues and returns a parsed object.

    .DESCRIPTION
        AI models sometimes produce slightly malformed JSON. This function applies
        a series of repairs to extract and parse the JSON:
        1. Strip markdown code fences (```json ... ```)
        2. Replace Infinity/NaN with null
        3. Fix empty keys ("","key" → "key")
        4. Fix double commas (,,)
        5. Fix trailing commas before closing braces/brackets
        6. Extract JSON object if surrounded by text
        7. Parse and return

    .PARAMETER Response
        The raw string response from the AI model.

    .OUTPUTS
        PSCustomObject — the parsed JSON object.

    .EXAMPLE
        $parsed = Repair-JsonResponse -Response $rawResponse
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Response
    )

    if (-not $Response -or $Response.Trim().Length -eq 0) {
        throw "Empty response — nothing to parse"
    }

    $json = $Response

    # 1. Strip markdown code fences
    if ($json -match '(?s)^\s*```(?:json)?\s*\n(.+?)\n\s*```\s*$') {
        $json = $Matches[1]
    }

    # 2. Replace Infinity/NaN with null
    $json = $json -replace '(?<=[\s,:[\{])-?Infinity', 'null' -replace '(?<=[\s,:[\{])NaN', 'null'

    # 3. Fix stray double-quotes before key names: ,""key" → ,"key" or {""key" → {"key"
    # This is the Ahmed Samy bug: GPT emits an extra quote creating ""key_name"
    $json = $json -replace '([,{])""([a-zA-Z_][a-zA-Z0-9_]*)"', '$1"$2"'

    # 4. Fix empty keys: "","key" → "key" or "": value, "key" → "key"
    # Pattern: ,"", or "":"...", (empty string key with no proper value)
    $json = $json -replace ',""\s*,', ','
    $json = $json -replace '\{""\s*,', '{'
    # Pattern: "":"value"," → remove the empty-key pair
    $json = $json -replace '""\s*:\s*"[^"]*"\s*,', ''
    # Pattern: ,"":"value"} → }
    $json = $json -replace ',\s*""\s*:\s*"[^"]*"\s*}', '}'

    # 5. Fix double/triple commas
    $json = $json -replace ',\s*,+', ','

    # 6. Fix trailing commas before } or ]
    $json = $json -replace ',\s*([}\]])', '$1'

    # 7. Extract JSON object if response contains non-JSON prefix/suffix
    if ($json[0] -ne '{') {
        if ($json -match '(?s)(\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})\s*$') {
            $json = $Matches[1]
        }
        elseif ($json -match '(?s)(\{.+\})') {
            $json = $Matches[1]
        }
        else {
            throw "Response does not contain valid JSON"
        }
    }

    # 8. Parse
    try {
        return ($json | ConvertFrom-Json)
    }
    catch {
        $firstError = $_.Exception.Message

        # 9. Attempt truncation repair — close open strings and structures
        $repaired = $json
        # If the last non-whitespace char isn't } or ], the response was likely truncated
        $trimmed = $repaired.TrimEnd()
        if ($trimmed[-1] -notin '}', ']') {
            # Close any open string
            $quoteCount = ($trimmed.ToCharArray() | Where-Object { $_ -eq '"' }).Count
            if ($quoteCount % 2 -ne 0) {
                $repaired = $trimmed + '"'
            }
            # Count open braces/brackets and close them
            $openBraces = ($repaired.ToCharArray() | Where-Object { $_ -eq '{' }).Count -
                          ($repaired.ToCharArray() | Where-Object { $_ -eq '}' }).Count
            $openBrackets = ($repaired.ToCharArray() | Where-Object { $_ -eq '[' }).Count -
                            ($repaired.ToCharArray() | Where-Object { $_ -eq ']' }).Count
            # Remove any trailing comma
            $repaired = $repaired -replace ',\s*$', ''
            $repaired += (']' * [math]::Max(0, $openBrackets)) + ('}' * [math]::Max(0, $openBraces))
        }

        try {
            return ($repaired | ConvertFrom-Json)
        }
        catch {
            throw "JSON parse failed after repair: $firstError"
        }
    }
}