feat(ai): add PixelRAG platform probe readiness
This commit is contained in:
@@ -17,6 +17,7 @@ const DEFAULT_OUTPUT_DIR = 'runtime_artifacts/pixelrag_visual_evidence';
|
||||
const DEFAULT_TIMEOUT_MS = 30000;
|
||||
const DEFAULT_SETTLE_MS = 500;
|
||||
const DEFAULT_MAX_TILES = 80;
|
||||
const FORBIDDEN_CONTEXT_HEADER_TOKENS = ['cookie', 'authorization', 'token', 'secret', 'key'];
|
||||
|
||||
const ALLOWED_PLATFORM_HOSTS = {
|
||||
momo: new Set(['m.momoshop.com.tw', 'www.momoshop.com.tw']),
|
||||
@@ -184,6 +185,30 @@ function positiveInt(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function safePublicBrowserContext(manifest) {
|
||||
const input = manifest.public_browser_context || {};
|
||||
const headers = {};
|
||||
const rawHeaders = input.extra_http_headers || {};
|
||||
for (const [key, value] of Object.entries(rawHeaders)) {
|
||||
const cleanKey = String(key || '').trim();
|
||||
const lowerKey = cleanKey.toLowerCase();
|
||||
if (!cleanKey || FORBIDDEN_CONTEXT_HEADER_TOKENS.some((token) => lowerKey.includes(token))) {
|
||||
continue;
|
||||
}
|
||||
headers[cleanKey] = String(value || '');
|
||||
}
|
||||
return {
|
||||
context_policy: 'public_empty_browser_context_no_login',
|
||||
locale: String(input.locale || 'zh-TW'),
|
||||
timezone_id: String(input.timezone_id || input.timezoneId || 'Asia/Taipei'),
|
||||
extra_http_headers: headers,
|
||||
credentialed_session_allowed: false,
|
||||
storage_state_allowed: false,
|
||||
raw_cookie_or_session_read_allowed: false,
|
||||
login_allowed: false,
|
||||
};
|
||||
}
|
||||
|
||||
function buildTilePlan({ width, height, tileWidth, tileHeight, maxTiles }) {
|
||||
const tiles = [];
|
||||
const tilesX = Math.max(1, Math.ceil(width / tileWidth));
|
||||
@@ -273,6 +298,7 @@ function buildBaseArtifact(manifest, options, errors = []) {
|
||||
tile_size: { width: tileWidth, height: tileHeight },
|
||||
tile_plan: tilePlan,
|
||||
files: [],
|
||||
public_browser_context: safePublicBrowserContext(manifest),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -288,8 +314,19 @@ async function capture(manifest, options, artifact) {
|
||||
launchOptions.executablePath = chromePath;
|
||||
}
|
||||
|
||||
const publicContext = safePublicBrowserContext(manifest);
|
||||
const contextOptions = {
|
||||
ignoreHTTPSErrors: true,
|
||||
viewport,
|
||||
locale: publicContext.locale,
|
||||
timezoneId: publicContext.timezone_id,
|
||||
};
|
||||
if (Object.keys(publicContext.extra_http_headers).length) {
|
||||
contextOptions.extraHTTPHeaders = publicContext.extra_http_headers;
|
||||
}
|
||||
|
||||
const browser = await chromium.launch(launchOptions);
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true, viewport });
|
||||
const context = await browser.newContext(contextOptions);
|
||||
const page = await context.newPage();
|
||||
try {
|
||||
const response = await page.goto(manifest.capture_target.url, {
|
||||
|
||||
@@ -23,6 +23,7 @@ DEFAULT_OUTPUT_DIR = "runtime_artifacts/pixelrag_visual_evidence"
|
||||
DEFAULT_TIMEOUT_MS = 30000
|
||||
DEFAULT_SETTLE_MS = 500
|
||||
DEFAULT_MAX_TILES = 80
|
||||
FORBIDDEN_CONTEXT_HEADER_TOKENS = ("cookie", "authorization", "token", "secret", "key")
|
||||
ALLOWED_PLATFORM_HOSTS = {
|
||||
"momo": {"m.momoshop.com.tw", "www.momoshop.com.tw"},
|
||||
"pchome": {"24h.pchome.com.tw", "ecshweb.pchome.com.tw", "ecapi-cdn.pchome.com.tw"},
|
||||
@@ -123,6 +124,33 @@ def _build_tile_plan(
|
||||
}
|
||||
|
||||
|
||||
def _safe_public_browser_context(manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
raw_context = manifest.get("public_browser_context") or {}
|
||||
raw_headers = raw_context.get("extra_http_headers") or {}
|
||||
headers: dict[str, str] = {}
|
||||
if isinstance(raw_headers, dict):
|
||||
for key, value in raw_headers.items():
|
||||
clean_key = str(key or "").strip()
|
||||
lower_key = clean_key.lower()
|
||||
if not clean_key:
|
||||
continue
|
||||
if any(token in lower_key for token in FORBIDDEN_CONTEXT_HEADER_TOKENS):
|
||||
continue
|
||||
headers[clean_key] = str(value or "")
|
||||
return {
|
||||
"context_policy": "public_empty_browser_context_no_login",
|
||||
"locale": str(raw_context.get("locale") or "zh-TW"),
|
||||
"timezone_id": str(
|
||||
raw_context.get("timezone_id") or raw_context.get("timezoneId") or "Asia/Taipei"
|
||||
),
|
||||
"extra_http_headers": headers,
|
||||
"credentialed_session_allowed": False,
|
||||
"storage_state_allowed": False,
|
||||
"raw_cookie_or_session_read_allowed": False,
|
||||
"login_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def _output_paths(manifest: dict[str, Any], output_dir: str) -> dict[str, Path]:
|
||||
target = manifest.get("capture_target") or {}
|
||||
manifest_id = manifest.get("manifest_id") or _safe_name(
|
||||
@@ -187,6 +215,7 @@ def _base_artifact(
|
||||
max_tiles=_positive_int(args.max_tiles, DEFAULT_MAX_TILES),
|
||||
),
|
||||
"files": [],
|
||||
"public_browser_context": _safe_public_browser_context(manifest),
|
||||
}
|
||||
|
||||
|
||||
@@ -205,10 +234,19 @@ def _capture(
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(headless=True)
|
||||
context = browser.new_context(ignore_https_errors=True, viewport={
|
||||
"width": viewport["width"],
|
||||
"height": viewport["height"],
|
||||
})
|
||||
public_context = _safe_public_browser_context(manifest)
|
||||
context_kwargs = {
|
||||
"ignore_https_errors": True,
|
||||
"viewport": {
|
||||
"width": viewport["width"],
|
||||
"height": viewport["height"],
|
||||
},
|
||||
"locale": public_context["locale"],
|
||||
"timezone_id": public_context["timezone_id"],
|
||||
}
|
||||
if public_context["extra_http_headers"]:
|
||||
context_kwargs["extra_http_headers"] = public_context["extra_http_headers"]
|
||||
context = browser.new_context(**context_kwargs)
|
||||
page = context.new_page()
|
||||
try:
|
||||
response = page.goto(target["url"], wait_until=args.wait_until, timeout=timeout_ms)
|
||||
|
||||
65
scripts/ops/report_pixelrag_platform_probe.py
Normal file
65
scripts/ops/report_pixelrag_platform_probe.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report PixelRAG platform probe readiness from barrier receipts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from services.pixelrag_platform_probe_service import ( # noqa: E402
|
||||
build_pixelrag_platform_probe_readiness,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="輸出 PixelRAG platform probe readiness 的機器可讀讀回。"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--artifact-root",
|
||||
help="PixelRAG visual evidence artifact root;預設使用 production/container 設定。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vlm-receipt-root",
|
||||
help="PixelRAG VLM replay receipt root;預設使用 production/container 設定。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
action="append",
|
||||
dest="platforms",
|
||||
help="限制平台,可重複指定,例如 --platform shopee_tw --platform coupang_tw。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-age-hours",
|
||||
type=int,
|
||||
default=168,
|
||||
help="receipt 最大新鮮度小時數。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit",
|
||||
type=int,
|
||||
default=50,
|
||||
help="最多輸出 probe candidate 數。",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = build_pixelrag_platform_probe_readiness(
|
||||
artifact_root=args.artifact_root,
|
||||
vlm_receipt_root=args.vlm_receipt_root,
|
||||
platform=tuple(args.platforms or ()),
|
||||
max_age_hours=args.max_age_hours,
|
||||
limit=args.limit,
|
||||
)
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0 if payload.get("success") else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user