Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
133 lines
5.1 KiB
Bash
133 lines
5.1 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
input_path="${1:?input path is required}"
|
|
research_dir="${2:?research directory is required}"
|
|
attachment_urls_path="$research_dir/attachment-urls.json"
|
|
|
|
node -e '
|
|
const fs = require("node:fs");
|
|
const auto = require(process.env.RUNNER_TEMP + "/ai-automation.cjs");
|
|
const input = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
const urls = auto.extractGithubUserAttachmentAssetUrls(input);
|
|
fs.writeFileSync(process.argv[2], JSON.stringify(urls) + "\n");
|
|
' "$input_path" "$attachment_urls_path"
|
|
|
|
attachment_count="$(node -p 'require(process.argv[1]).length' "$attachment_urls_path")"
|
|
if (( attachment_count > 4 )); then
|
|
echo "Too many GitHub image attachments for bounded research: ${attachment_count}" >&2
|
|
exit 1
|
|
fi
|
|
if (( attachment_count == 0 )); then
|
|
cp "$input_path" "$research_dir/input.json"
|
|
exit 0
|
|
fi
|
|
|
|
mkdir -p "$research_dir/attachments"
|
|
attachment_kinds_path="$research_dir/attachment-kinds.txt"
|
|
: > "$attachment_kinds_path"
|
|
image_count=0
|
|
for (( index=0; index<attachment_count; index++ )); do
|
|
source_url="$(node -e '
|
|
process.stdout.write(require(process.argv[1])[Number(process.argv[2])]);
|
|
' "$attachment_urls_path" "$index")"
|
|
headers_path="$(mktemp "${RUNNER_TEMP}/ai-attachment-headers.XXXXXX")"
|
|
curl --proto '=https' --tlsv1.2 --retry 3 --retry-all-errors \
|
|
--connect-timeout 3 --max-time 20 --max-redirs 0 -fsS \
|
|
-D "$headers_path" -o /dev/null "$source_url"
|
|
kind="$(node -e '
|
|
const fs = require("node:fs");
|
|
const auto = require(process.env.RUNNER_TEMP + "/ai-automation.cjs");
|
|
process.stdout.write(auto.classifyGithubUserAttachmentRedirect(
|
|
fs.readFileSync(process.argv[1], "utf8"),
|
|
));
|
|
' "$headers_path")"
|
|
rm -f "$headers_path"
|
|
if [[ "$kind" != "image" && "$kind" != "unsupported_media" ]]; then
|
|
echo "GitHub attachment did not provide a trusted image/video/audio redirect." >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$kind" >> "$attachment_kinds_path"
|
|
if [[ "$kind" == "image" ]]; then
|
|
image_count=$((image_count + 1))
|
|
fi
|
|
done
|
|
|
|
container_name="ai-research-imgproxy-${GITHUB_RUN_ID}-${GITHUB_JOB}"
|
|
if (( image_count > 0 )); then
|
|
docker run -d --rm --name "$container_name" \
|
|
--cap-drop=ALL --security-opt=no-new-privileges --read-only \
|
|
--memory=512m --cpus=1 --pids-limit=128 \
|
|
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
|
|
-p 127.0.0.1:18081:8080 \
|
|
-e IMGPROXY_ALLOWED_SOURCES=https://github.com/user-attachments/assets/ \
|
|
-e IMGPROXY_ALLOW_LOOPBACK_SOURCE_ADDRESSES=false \
|
|
-e IMGPROXY_ALLOW_LINK_LOCAL_SOURCE_ADDRESSES=false \
|
|
-e IMGPROXY_ALLOW_PRIVATE_SOURCE_ADDRESSES=false \
|
|
-e IMGPROXY_MAX_SRC_FILE_SIZE=10485760 \
|
|
-e IMGPROXY_MAX_SRC_RESOLUTION=50 \
|
|
-e IMGPROXY_MAX_RESULT_DIMENSION=4096 \
|
|
-e IMGPROXY_MAX_REDIRECTS=2 \
|
|
-e IMGPROXY_MAX_ANIMATION_FRAMES=1 \
|
|
-e IMGPROXY_ALWAYS_RASTERIZE_SVG=true \
|
|
-e IMGPROXY_ALLOW_SECURITY_OPTIONS=false \
|
|
-e IMGPROXY_COOKIE_PASSTHROUGH=false \
|
|
-e IMGPROXY_COOKIE_PASSTHROUGH_ALL=false \
|
|
"$AI_RESEARCH_IMGPROXY_IMAGE" >/dev/null
|
|
fi
|
|
stop_imgproxy() {
|
|
if (( image_count > 0 )); then
|
|
docker stop "$container_name" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap stop_imgproxy EXIT
|
|
|
|
for (( index=0; index<attachment_count; index++ )); do
|
|
kind="$(sed -n "$((index + 1))p" "$attachment_kinds_path")"
|
|
if [[ "$kind" != "image" ]]; then
|
|
continue
|
|
fi
|
|
source_url="$(node -e '
|
|
process.stdout.write(require(process.argv[1])[Number(process.argv[2])]);
|
|
' "$attachment_urls_path" "$index")"
|
|
encoded_source="$(node -e '
|
|
process.stdout.write(Buffer.from(process.argv[1]).toString("base64url"));
|
|
' "$source_url")"
|
|
output_path="$research_dir/attachments/issue-image-$((index + 1)).png"
|
|
curl --retry 8 --retry-all-errors --retry-delay 1 --retry-max-time 45 \
|
|
--connect-timeout 3 --max-time 30 -fsS \
|
|
"http://127.0.0.1:18081/unsafe/${encoded_source}.png" \
|
|
-o "$output_path"
|
|
node -e '
|
|
const fs = require("node:fs");
|
|
const expected = Buffer.from("89504e470d0a1a0a", "hex");
|
|
const actual = fs.readFileSync(process.argv[1]).subarray(0, expected.length);
|
|
if (!actual.equals(expected)) throw new Error("imgproxy did not produce PNG output");
|
|
' "$output_path"
|
|
done
|
|
|
|
stop_imgproxy
|
|
trap - EXIT
|
|
# The single-quoted program intentionally contains JavaScript template syntax.
|
|
# shellcheck disable=SC2016
|
|
node -e '
|
|
const fs = require("node:fs");
|
|
const auto = require(process.env.RUNNER_TEMP + "/ai-automation.cjs");
|
|
const input = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
const urls = require(process.argv[2]);
|
|
const kinds = fs.readFileSync(process.argv[3], "utf8").trim().split("\n");
|
|
const attachments = urls.map((sourceUrl, index) => ({
|
|
sourceUrl,
|
|
kind: kinds[index],
|
|
...(kinds[index] === "image"
|
|
? { relativePath: `attachments/issue-image-${index + 1}.png` }
|
|
: {}),
|
|
}));
|
|
fs.writeFileSync(
|
|
process.argv[4],
|
|
JSON.stringify(auto.rewriteExternalResearchInputAttachments(input, attachments), null, 2) + "\n",
|
|
);
|
|
' "$input_path" "$attachment_urls_path" "$attachment_kinds_path" "$research_dir/input.json"
|
|
rm -f "$attachment_urls_path" "$attachment_kinds_path"
|