Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion scripts/run-and-publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ REGION=""
RUNS=100
DRY_RUN=0
NO_RESET=0
BASELAYER_SELFHOST_METAL_JSON=""

while [[ $# -gt 0 ]]; do
case "$1" in
Expand All @@ -31,7 +32,7 @@ while [[ $# -gt 0 ]]; do
done

case "$REGION" in
us-east) PROVIDERS="steel,kernel,kernel-headful,hyperbrowser,anchorbrowser,browser-use" ;;
us-east) PROVIDERS="steel,kernel,kernel-headful,hyperbrowser,anchorbrowser,browser-use,baselayer" ;;
us-west) PROVIDERS="notte,browserbase" ;;
"") echo "[ERROR] --region us-east|us-west required" >&2; exit 2 ;;
*) echo "[ERROR] invalid --region: $REGION (want us-east or us-west)" >&2; exit 2 ;;
Expand All @@ -40,6 +41,109 @@ esac
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"

cleanup_baselayer_selfhost() {
if [[ -z "${BASELAYER_SELFHOST_METAL_JSON:-}" || ! -f "$BASELAYER_SELFHOST_METAL_JSON" ]]; then
return
fi
node - "$BASELAYER_SELFHOST_METAL_JSON" <<'NODE'
const fs = require("fs");
const meta = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
if (!meta.instanceId || !meta.region) process.exit(0);
console.log(`[INFO] terminating BaseLayer self-host metal ${meta.instanceId} in ${meta.region}`);
const { spawnSync } = require("child_process");
spawnSync("aws", [
"ec2",
"terminate-instances",
"--profile",
process.env.BASELAYER_AWS_PROFILE || "baselayer",
"--region",
meta.region,
"--instance-ids",
meta.instanceId,
], { stdio: "inherit" });
NODE
}
trap cleanup_baselayer_selfhost EXIT

provider_enabled() {
local needle="$1"
IFS=',' read -ra enabled <<< "$PROVIDERS"
for p in "${enabled[@]}"; do
[[ "$p" == "$needle" ]] && return 0
done
return 1
}

setup_baselayer_selfhost() {
if ! provider_enabled "baselayer"; then
return
fi
if [[ -n "${BASELAYER_API_URL:-}" ]]; then
echo "[INFO] BASELAYER_API_URL already set; using existing BaseLayer endpoint"
return
fi
if [[ "${BASELAYER_AUTO_SELFHOST:-1}" != "1" ]]; then
echo "[ERROR] baselayer provider is enabled but BASELAYER_API_URL is unset." >&2
echo "[ERROR] Set BASELAYER_API_URL or set BASELAYER_AUTO_SELFHOST=1 to provision from the BaseLayer repo." >&2
exit 1
fi
if [[ $DRY_RUN -eq 1 && "${BASELAYER_DRY_RUN_SELFHOST:-0}" != "1" ]]; then
echo "[INFO] --dry-run: skipping BaseLayer auto-selfhost provisioning."
echo "[INFO] Set BASELAYER_DRY_RUN_SELFHOST=1 to allow dry-run provisioning."
return
fi

local ps
ps="$(command -v pwsh || command -v powershell || true)"
if [[ -z "$ps" ]]; then
echo "[ERROR] BaseLayer auto-selfhost requires PowerShell 7+ (pwsh) because the BaseLayer AWS wrapper is PowerShell." >&2
exit 1
fi
if ! command -v aws >/dev/null 2>&1; then
echo "[ERROR] BaseLayer auto-selfhost requires AWS CLI v2 on the BrowserArena runner." >&2
exit 1
fi

local base_repo="${BASELAYER_REPO:-https://github.com/Lasdw6/BaseLayer.git}"
local base_ref="${BASELAYER_REF:-main}"
local base_dir="${BASELAYER_SELFHOST_REPO_DIR:-$REPO_ROOT/.tmp/baselayer-selfhost-repo}"
local out_dir="${BASELAYER_SELFHOST_OUT_DIR:-$REPO_ROOT/.tmp/baselayer-selfhost}"
local aws_region="${BASELAYER_AWS_REGION:-us-east-2}"
local smoke_runs="${BASELAYER_SELFHOST_SMOKE_RUNS:-1}"
local smoke_concurrency="${BASELAYER_SELFHOST_SMOKE_CONCURRENCY:-1}"
local runtime_profile="${BASELAYER_RUNTIME_PROFILE:-baselayer-firecracker-headless-shell}"

echo "[INFO] provisioning BaseLayer self-host from $base_repo ref=$base_ref region=$aws_region"
rm -rf "$base_dir"
git clone --depth 1 --branch "$base_ref" "$base_repo" "$base_dir"

"$ps" -NoProfile -ExecutionPolicy Bypass -File "$base_dir/scripts/bench/run-browserarena-selfhosted.ps1" \
-Mode local \
-AwsProfile "${BASELAYER_AWS_PROFILE:-baselayer}" \
-Region "$aws_region" \
-BaseLayerRepo "$base_repo" \
-BaseLayerRef "$base_ref" \
-BrowserArenaPath "$REPO_ROOT" \
-Target "${BASELAYER_SELFHOST_TARGET:-https://example.com}" \
-RuntimeProfile "$runtime_profile" \
-Concurrency "$smoke_concurrency" \
-Runs "$smoke_runs" \
-Repeats 1 \
-KeepMetal \
-OutDir "$out_dir"

BASELAYER_SELFHOST_METAL_JSON="$(find "$out_dir" -path '*/repeat-1/metal.json' -type f | sort | tail -n 1)"
if [[ -z "$BASELAYER_SELFHOST_METAL_JSON" || ! -f "$BASELAYER_SELFHOST_METAL_JSON" ]]; then
echo "[ERROR] BaseLayer self-host setup did not produce metal.json under $out_dir" >&2
exit 1
fi
export BASELAYER_API_URL
BASELAYER_API_URL="$(node -e 'const fs=require("fs"); const m=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(`http://${m.publicIp}:3000`);' "$BASELAYER_SELFHOST_METAL_JSON")"
export BASELAYER_RUNTIME_PROFILE="$runtime_profile"
rm -rf "results/hello-browser/baselayer/$(date -u +%F)"
echo "[INFO] BaseLayer self-host ready at $BASELAYER_API_URL"
}

mkdir -p logs
LOG_FILE="logs/cron-$(date -u +%Y-%m-%dT%H-%M-%SZ)-${REGION}.log"
exec > >(tee -a "$LOG_FILE") 2>&1
Expand All @@ -65,6 +169,8 @@ else
echo "[WARN] no .env found; provider env vars must already be exported"
fi

setup_baselayer_selfhost

# Sanity: at least one provider in this region has its env var set. Mirrors
# the env-var map in src/providers/index.ts.
have_any=0
Expand All @@ -76,6 +182,7 @@ for p in "${PROV_LIST[@]}"; do
hyperbrowser) v="${HYPERBROWSER_API_KEY:-}" ;;
anchorbrowser) v="${ANCHORBROWSER_API_KEY:-}" ;;
browser-use) v="${BROWSER_USE_API_KEY:-}" ;;
baselayer) v="${BASELAYER_API_URL:-}" ;;
notte) v="${NOTTE_API_KEY:-}" ;;
browserbase) v="${BROWSERBASE_API_KEY:-}" ;;
*) echo "[ERROR] unknown provider in region map: $p" >&2; exit 2 ;;
Expand Down
79 changes: 79 additions & 0 deletions src/providers/baselayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ProviderClient, ProviderSession } from "../types.js";
import { requireEnv } from "../utils/env.js";

type BaseLayerSessionResponse = {
id?: string;
sessionId?: string;
cdpUrl?: string;
connectUrl?: string;
webSocketDebuggerUrl?: string;
};

function optionalEnv(name: string): string | undefined {
const value = process.env[name]?.trim();
return value ? value : undefined;
}

export class BaseLayerProvider implements ProviderClient {
readonly name = "BASELAYER";

computeCost(): number {
return 0;
}

private apiUrl(): string {
return requireEnv("BASELAYER_API_URL").replace(/\/$/, "");
}

private authHeaders(): Record<string, string> {
const headers: Record<string, string> = {};
const apiKey = optionalEnv("BASELAYER_API_KEY");
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
return headers;
}

private jsonHeaders(): Record<string, string> {
return {
...this.authHeaders(),
"Content-Type": "application/json",
};
}

async create(): Promise<ProviderSession> {
const runtimeProfile = optionalEnv("BASELAYER_RUNTIME_PROFILE");
const body: Record<string, string> = { browser: "chromium" };
if (runtimeProfile) body.runtimeProfile = runtimeProfile;

const response = await fetch(`${this.apiUrl()}/v1/sessions`, {
method: "POST",
headers: this.jsonHeaders(),
body: JSON.stringify(body),
signal: AbortSignal.timeout(120_000),
});

if (!response.ok) {
throw new Error(`BaseLayer create failed: HTTP ${response.status} - ${await response.text()}`);
}

const data = (await response.json()) as BaseLayerSessionResponse;
const id = data.id ?? data.sessionId;
const cdpUrl = data.cdpUrl ?? data.connectUrl ?? data.webSocketDebuggerUrl;
if (!id || !cdpUrl) {
throw new Error(`Invalid BaseLayer session response: ${JSON.stringify(data)}`);
}

return { id, cdpUrl };
}

async release(id: string): Promise<void> {
const response = await fetch(`${this.apiUrl()}/v1/sessions/${encodeURIComponent(id)}`, {
method: "DELETE",
headers: this.authHeaders(),
signal: AbortSignal.timeout(30_000),
});

if (!response.ok && response.status !== 404) {
throw new Error(`BaseLayer release failed: HTTP ${response.status} - ${await response.text()}`);
}
}
}
3 changes: 3 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HyperbrowserProvider } from "./hyperbrowser.js";
import { KernelProvider, KernelHeadfulProvider } from "./kernel.js";
import { NotteProvider } from "./notte.js";
import { BrowserUseProvider } from "./browser-use.js";
import { BaseLayerProvider } from "./baselayer.js";

export function getRequiredEnvVarsForProvider(name: string): string[] {
const key = name.trim().toLowerCase();
Expand All @@ -20,6 +21,7 @@ export function getRequiredEnvVarsForProvider(name: string): string[] {
if (key === "notte") return ["NOTTE_API_KEY"];
if (key === "browser-use" || key === "browseruse" || key === "bu")
return ["BROWSER_USE_API_KEY"];
if (key === "baselayer" || key === "base-layer") return ["BASELAYER_API_URL"];
throw new Error(`Unknown provider: ${name}`);
}

Expand All @@ -42,5 +44,6 @@ export function resolveProvider(name: string): ProviderClient {
if (key === "notte") return new NotteProvider();
if (key === "browser-use" || key === "browseruse" || key === "bu")
return new BrowserUseProvider();
if (key === "baselayer" || key === "base-layer") return new BaseLayerProvider();
throw new Error(`Unknown provider: ${name}`);
}
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export type ProviderName =
| "KERNEL"
| "KERNEL_HEADFUL"
| "NOTTE"
| "BROWSER_USE";
| "BROWSER_USE"
| "BASELAYER";

export type ProviderSession = {
id: string;
Expand Down
1 change: 1 addition & 0 deletions web/components/leaderboard-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const PROVIDER_LOGOS: Record<string, string> = {
KERNEL: "/logos/kernel.png",
STEEL: "/logos/steel.png",
BROWSER_USE: "/logos/browseruse.png",
BASELAYER: "/logos/baselayer.svg",
};

type SortDirection = "asc" | "desc";
Expand Down
6 changes: 6 additions & 0 deletions web/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ const PROVIDER_META: Record<
},
STEEL: { displayName: "Steel", url: "https://www.steel.dev", browserRegion: "us-east-1" },
BROWSER_USE: { displayName: "Browser Use", url: "https://www.browser-use.com", browserRegion: "us-east-1" },
BASELAYER: {
displayName: "BaseLayer",
url: "https://github.com/Lasdw6/BaseLayer",
browserRegion: "self-hosted AWS m5zn.metal",
disclaimer: "Self-hosted on AWS m5zn.metal; not a managed cloud provider.",
},
};

function median(values: number[]): number {
Expand Down
42 changes: 42 additions & 0 deletions web/public/logos/baselayer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.