skills/agent-evals/scripts/Get-EvalText.ps1

function Get-EvalText {
    <#
    .SYNOPSIS
        Reads one UTF-8 evaluation input under a byte limit.
    .DESCRIPTION
        Checks the open file's length, then bounds the read itself to handle
        growth without allocating an unbounded string. Recognizes Unicode BOMs.
        This is an ingestion limit, not filesystem containment or a run timeout.
    .PARAMETER LiteralPath
        Exact path to the definition, sample, or reply file.
    .PARAMETER MaxBytes
        Maximum bytes in this file, including its byte-order mark if present.
    .EXAMPLE
        Get-EvalText -LiteralPath 'sample-1.txt' -MaxBytes 1MB
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $LiteralPath,

        [Parameter(Mandatory)]
        [ValidateRange(1, 104857600)]
        [int] $MaxBytes
    )

    $stream = [IO.File]::Open($LiteralPath, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read)
    try {
        $tooLarge = "EvalInputTooLarge: '$LiteralPath' exceeds the $MaxBytes-byte per-file limit."
        if ($stream.Length -gt $MaxBytes) { throw [IO.InvalidDataException]::new($tooLarge) }
        $content = [IO.MemoryStream]::new()
        try {
            # A fixed buffer bounds growth checks without allocating the maximum allowed size up front.
            $buffer = [byte[]]::new(4096)
            do {
                $count = [int][Math]::Min($buffer.Length, $MaxBytes - $content.Length + 1)
                $read = $stream.Read($buffer, 0, $count)
                if ($content.Length + $read -gt $MaxBytes) {
                    throw [IO.InvalidDataException]::new($tooLarge)
                }
                $content.Write($buffer, 0, $read)
            } while ($read -gt 0)
            $content.Position = 0
            $reader = [IO.StreamReader]::new($content, [Text.Encoding]::UTF8, $true)
            try { $reader.ReadToEnd() }
            finally { $reader.Dispose() }
        }
        finally { $content.Dispose() }
    }
    finally { $stream.Dispose() }
}