private/FoundationaLLM-Core.ps1


$global:InstanceId = ""
$global:CoreAPIBaseUrl = ""
$global:ManagementAPIBaseUrl = ""

$global:CoreAPIAccessToken = ""
$global:ManagementAPIAccessToken = ""
$global:ACADynamicSessionsAccessToken = ""

function Test-JWTExpired {
    param(
        [string]$jwt
    )
    try {

        return $true

        if ($jwt -eq "") {
            return $true
        }
        $parts = $jwt.split(".")
        if ($parts.Length -ne 3) {
            Write-Warning "Invalid JWT format."
            return $true
        }
        $body = $parts[1]
        $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($body.Replace('-', '+').Replace('_', '/').PadRight(($body.Length / 4 * 4) + ($body.Length % 4 -eq 0 ? 0 : 4), '=')))
        $json = ConvertFrom-Json $decoded
        $expirationTime = [datetime]::UnixEpoch.AddSeconds($json.exp)
        $currentTime = [datetime]::UtcNow
        Write-Host "Current time: $currentTime | JWT expiration time: $expirationTime" -ForegroundColor Green
        return $expirationTime -lt $currentTime
    } catch {
        Write-Warning "Error decoding or parsing JWT: $($_.Exception.Message)"
        return $true
    }
}

function Get-ManagementAPIAccessToken {
    $accessToken = az account get-access-token `
        --scope "api://FoundationaLLM-Management/Data.Manage" `
        --output  tsv `
        --query accessToken
    return $accessToken
}

function Test-ManagementAPIAccessToken {
    if (Test-JWTExpired $global:ManagementAPIAccessToken) {
        $global:ManagementAPIAccessToken = Get-ManagementAPIAccessToken
    }
}

function Test-CoreAPIAccessToken {
    if (Test-JWTExpired $global:CoreAPIAccessToken) {
        $global:CoreAPIAccessToken = Get-CoreAPIAccessToken
    }
}

function Get-CoreAPIAccessToken {
    $accessToken = az account get-access-token `
        --scope "api://FoundationaLLM-Core/Data.Read" `
        --output  tsv `
        --query accessToken
    return $accessToken
}

function Test-CoreAPIAccessToken {
    if (Test-JWTExpired $global:CoreAPIAccessToken) {
        $global:CoreAPIAccessToken = Get-CoreAPIAccessToken
    }
}

function Get-ACADynamicSessionsAccessToken {
    $accessToken = az account get-access-token `
        --resource "https://dynamicsessions.io" `
        --output  tsv `
        --query accessToken
    return $accessToken
}

function Test-ACADynamicSessionsAccessToken {
    if (Test-JWTExpired $global:ACADynamicSessionsAccessToken) {
        $global:ACADynamicSessionsAccessToken = Get-ACADynamicSessionsAccessToken
    }
}

function Get-ManagementAPIBaseUri {
    return [System.Uri]::new([System.Uri]::new($ManagementAPIBaseUrl), $global:ManagementAPIInstanceRelativeUri)
}

function Get-CoreAPIBaseUri {
    return [System.Uri]::new([System.Uri]::new($CoreAPIBaseUrl), $global:CoreAPIInstanceRelativeUri)
}

function Get-ObjectId {
    param (
        [string]$Name,
        [string]$Type
    )

    return "/instances/$($global:InstanceId)/providers/$($Type)/$($Name)"
}

function Get-ResourceObjectIds {
    param (
        [array]$Resources
    )

    $resourceObjectIds = [ordered]@{}
    $Resources | ForEach-Object {
        $resource = $_
        $resourceObjectId = (Get-ObjectId -Name $resource.name -Type $resource.type)
        $resourceObjectIds[$resourceObjectId] = [ordered]@{}
        $resourceObjectIds[$resourceObjectId]["object_id"] = $resourceObjectId
        $resourceObjectIds[$resourceObjectId]["properties"] = [ordered]@{}
        if ($resource.Contains("role") ){
            $resourceObjectIds[$resourceObjectId]["properties"]["object_role"] = $resource.role
        }
        if ($resource.Contains("properties") ){
            $resource["properties"] | ForEach-Object {
                $propertyList = $_
                $currentDict = $resourceObjectIds[$resourceObjectId]["properties"]
                for ($i = 0; $i -lt $propertyList.Length - 2; $i++) {
                    if ($currentDict.Contains($propertyList[$i])) {
                        $currentDict = $currentDict[$propertyList[$i]]
                    } else {
                        $currentDict[$propertyList[$i].ToString()] = [ordered]@{}
                        $currentDict = $currentDict[$propertyList[$i]]
                    }
                }
                $currentDict[$propertyList[$i].ToString()] = $propertyList[$i + 1]
            }
        }
    }

    return $resourceObjectIds
}

function Invoke-ManagementAPI {
    param (
        [string]$Method,
        [string]$RelativeUri,
        [hashtable]$Body = $null,
        [hashtable]$Headers = $null,
        [hashtable]$Form = $null,
        [System.Net.Http.MultipartFormDataContent]$MultipartContent = $null,
        [switch]$BinaryOutput
    )

    Write-Host "Calling Management API:"

    Test-ManagementAPIAccessToken

    if ($Headers -eq $null) {
        $Headers = @{
            "Authorization" = "Bearer $($global:ManagementAPIAccessToken)"
            "Content-Type"  = "application/json"
        }
    } else {
        $Headers["Authorization"] = "Bearer $($global:ManagementAPIAccessToken)"
    }

    $baseUri = Get-ManagementAPIBaseUri

    $uri = "$($baseUri.AbsoluteUri)/$($RelativeUri)"

    Write-Host "$($Method) $($uri)"

    # Handle MultipartFormDataContent (in-memory multipart data)
    if ($MultipartContent) {
        $client = [System.Net.Http.HttpClient]::new()
        $client.DefaultRequestHeaders.Clear()
        $client.DefaultRequestHeaders.Add("Authorization", "Bearer $($global:ManagementAPIAccessToken)")

        $response = switch ($Method.ToUpper()) {
            "POST" { $client.PostAsync($uri, $MultipartContent).Result }
            "PUT" { $client.PutAsync($uri, $MultipartContent).Result }
            default { throw "Unsupported HTTP method for MultipartContent: $Method" }
        }

        $responseContent = $response.Content.ReadAsStringAsync().Result

        if (-not $response.IsSuccessStatusCode) {
            throw "API call failed with status $($response.StatusCode): $responseContent"
        }

        if ($BinaryOutput) {
            return $response
        }

        if ([string]::IsNullOrWhiteSpace($responseContent)) {
            return $null
        }

        return $responseContent | ConvertFrom-Json
    }

    if ($Form) {

        $Headers["Content-Type"] = "multipart/form-data;boundary=----WebKitFormBoundaryABCDEFGHIJKLMONOPQRSTUVWXYZ"
        if ($BinaryOutput) {
            return Invoke-WebRequest `
                -Method $Method `
                -Uri $uri `
                -Form $Form `
                -Headers $Headers
        } else {
            return Invoke-RestMethod `
                -Method $Method `
                -Uri $uri `
                -Form $Form `
                -Headers $Headers
        }
    }

    if ($BinaryOutput) {
        return Invoke-WebRequest `
            -Method $Method `
            -Uri $uri `
            -Body ($Body | ConvertTo-Json -Depth 20) `
            -Headers $Headers
    }

    return Invoke-RestMethod `
        -Method $Method `
        -Uri $uri `
        -Body ($Body | ConvertTo-Json -Depth 20) `
        -Headers $Headers
}

function Invoke-CoreAPI {
    param (
        [string]$Method,
        [string]$RelativeUri,
        [hashtable]$Body = $null,
        [hashtable]$Headers = $null,
        [string]$FilePath = $null,
        [string]$FileContentType = $null
    )

    Write-Host "Calling Core API:"

    Test-CoreAPIAccessToken

    if ($Headers -eq $null) {
        $Headers = @{
            "Authorization" = "Bearer $($global:CoreAPIAccessToken)"
            "Content-Type"  = "application/json"
        }
    } else {
        $Headers["Authorization"] = "Bearer $($global:CoreAPIAccessToken)"
    }

    $baseUri = Get-CoreAPIBaseUri

    $uri = "$($baseUri.AbsoluteUri)/$($RelativeUri)"

    Write-Host "$($Method) $($uri)"
    if ($FilePath) {

        $FileStream = [System.IO.File]::OpenRead($FilePath)
        $FileContent = New-Object System.Net.Http.StreamContent($FileStream)
        $FileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($FileContentType)

        $Form = New-Object System.Net.Http.MultipartFormDataContent
        $Form.Add($FileContent, "file", [System.IO.Path]::GetFileName($FilePath))

        $Client = New-Object System.Net.Http.HttpClient
        $Client.DefaultRequestHeaders.Clear()
        foreach ($key in $Headers.Keys) {
            if ($key -ne 'Content-Type') {
                $Client.DefaultRequestHeaders.Add($key, $Headers[$key])
            }
        }

        $Response = $Client.PostAsync($uri, $Form).Result
        $ResponseContent = $Response.Content.ReadAsStringAsync().Result
        return $ResponseContent | ConvertFrom-Json
    }

    return Invoke-RestMethod `
        -Method $Method `
        -Uri  $uri `
        -Body ($Body | ConvertTo-Json -Depth 20) `
        -Headers $Headers
}

function Get-FileNameFromContentDisposition {
    param([string]$ContentDisposition)

    if ([string]::IsNullOrWhiteSpace($ContentDisposition)) { return $null }

    # Prefer RFC 5987 / 6266 filename*= (e.g. UTF-8''name%20here.pdf)
    if ($ContentDisposition -match '(?i)filename\*\s*=\s*(?<v>[^;]+)') {
        $v = $Matches.v.Trim()

        # Strip optional quotes around the whole value
        if ($v.StartsWith('"') -and $v.EndsWith('"')) { $v = $v.Trim('"') }

        # Split: charset'lang'value (lang may be empty)
        $parts = $v -split "'", 3
        if ($parts.Count -eq 3) {
            $encoded = $parts[2]
            return [Uri]::UnescapeDataString($encoded)
        }

        # If it wasn't in charset'lang' format, still try decoding as-is
        return [Uri]::UnescapeDataString($v)
    }

    # Fallback to filename=
    if ($ContentDisposition -match '(?i)filename\s*=\s*(?<v>[^;]+)') {
        $v = $Matches.v.Trim()
        if ($v.StartsWith('"') -and $v.EndsWith('"')) { $v = $v.Trim('"') }
        return $v
    }

    return $null
}