515 lines
16 KiB
Bash
Executable File
515 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}"
|
|
MODELS_MODE="${CLAWDBOT_E2E_MODELS:-both}" # both|openai|anthropic
|
|
E2E_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_E2E_PREVIOUS:-}"
|
|
SKIP_PREVIOUS="${CLAWDBOT_INSTALL_E2E_SKIP_PREVIOUS:-0}"
|
|
OPENAI_API_KEY="${OPENAI_API_KEY:-}"
|
|
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}"
|
|
ANTHROPIC_API_TOKEN="${ANTHROPIC_API_TOKEN:-}"
|
|
|
|
if [[ "$MODELS_MODE" != "both" && "$MODELS_MODE" != "openai" && "$MODELS_MODE" != "anthropic" ]]; then
|
|
echo "ERROR: CLAWDBOT_E2E_MODELS must be one of: both|openai|anthropic" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "$MODELS_MODE" == "both" ]]; then
|
|
if [[ -z "$OPENAI_API_KEY" ]]; then
|
|
echo "ERROR: CLAWDBOT_E2E_MODELS=both requires OPENAI_API_KEY." >&2
|
|
exit 2
|
|
fi
|
|
if [[ -z "$ANTHROPIC_API_TOKEN" && -z "$ANTHROPIC_API_KEY" ]]; then
|
|
echo "ERROR: CLAWDBOT_E2E_MODELS=both requires ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY." >&2
|
|
exit 2
|
|
fi
|
|
elif [[ "$MODELS_MODE" == "openai" && -z "$OPENAI_API_KEY" ]]; then
|
|
echo "ERROR: CLAWDBOT_E2E_MODELS=openai requires OPENAI_API_KEY." >&2
|
|
exit 2
|
|
elif [[ "$MODELS_MODE" == "anthropic" && -z "$ANTHROPIC_API_TOKEN" && -z "$ANTHROPIC_API_KEY" ]]; then
|
|
echo "ERROR: CLAWDBOT_E2E_MODELS=anthropic requires ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY." >&2
|
|
exit 2
|
|
fi
|
|
|
|
echo "==> Resolve npm versions"
|
|
LATEST_VERSION="$(npm view clawdbot version)"
|
|
if [[ -n "$E2E_PREVIOUS_VERSION" ]]; then
|
|
PREVIOUS_VERSION="$E2E_PREVIOUS_VERSION"
|
|
else
|
|
PREVIOUS_VERSION="$(node - <<'NODE'
|
|
const { execSync } = require("node:child_process");
|
|
const versions = JSON.parse(execSync("npm view clawdbot versions --json", { encoding: "utf8" }));
|
|
if (!Array.isArray(versions) || versions.length === 0) process.exit(1);
|
|
process.stdout.write(versions.length >= 2 ? versions[versions.length - 2] : versions[0]);
|
|
NODE
|
|
)"
|
|
fi
|
|
echo "latest=$LATEST_VERSION previous=$PREVIOUS_VERSION"
|
|
|
|
if [[ "$SKIP_PREVIOUS" == "1" ]]; then
|
|
echo "==> Skip preinstall previous (CLAWDBOT_INSTALL_E2E_SKIP_PREVIOUS=1)"
|
|
else
|
|
echo "==> Preinstall previous (forces installer upgrade path; avoids read() prompt)"
|
|
npm install -g "clawdbot@${PREVIOUS_VERSION}"
|
|
fi
|
|
|
|
echo "==> Run official installer one-liner"
|
|
curl -fsSL "$INSTALL_URL" | bash
|
|
|
|
echo "==> Verify installed version"
|
|
INSTALLED_VERSION="$(clawdbot --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
|
echo "installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
|
|
if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then
|
|
echo "ERROR: expected clawdbot@$LATEST_VERSION, got clawdbot@$INSTALLED_VERSION" >&2
|
|
exit 1
|
|
fi
|
|
|
|
set_image_model() {
|
|
local profile="$1"
|
|
shift
|
|
local candidate
|
|
for candidate in "$@"; do
|
|
if clawdbot --profile "$profile" models set-image "$candidate" >/dev/null 2>&1; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
echo "ERROR: could not set an image model (tried: $*)" >&2
|
|
return 1
|
|
}
|
|
|
|
set_agent_model() {
|
|
local profile="$1"
|
|
local candidate
|
|
shift
|
|
for candidate in "$@"; do
|
|
if clawdbot --profile "$profile" models set "$candidate" >/dev/null 2>&1; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
echo "ERROR: could not set agent model (tried: $*)" >&2
|
|
return 1
|
|
}
|
|
|
|
write_png_lr_rg() {
|
|
local out="$1"
|
|
node - <<'NODE' "$out"
|
|
const fs = require("node:fs");
|
|
const zlib = require("node:zlib");
|
|
|
|
const out = process.argv[2];
|
|
const width = 96;
|
|
const height = 64;
|
|
|
|
const crcTable = (() => {
|
|
const table = new Uint32Array(256);
|
|
for (let i = 0; i < 256; i++) {
|
|
let c = i;
|
|
for (let k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
table[i] = c >>> 0;
|
|
}
|
|
return table;
|
|
})();
|
|
function crc32(buf) {
|
|
let c = 0xffffffff;
|
|
for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
return (c ^ 0xffffffff) >>> 0;
|
|
}
|
|
function chunk(type, data) {
|
|
const typeBuf = Buffer.from(type, "ascii");
|
|
const len = Buffer.alloc(4);
|
|
len.writeUInt32BE(data.length, 0);
|
|
const crcBuf = Buffer.alloc(4);
|
|
crcBuf.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])), 0);
|
|
return Buffer.concat([len, typeBuf, data, crcBuf]);
|
|
}
|
|
|
|
const sig = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
const ihdr = Buffer.alloc(13);
|
|
ihdr.writeUInt32BE(width, 0);
|
|
ihdr.writeUInt32BE(height, 4);
|
|
ihdr[8] = 8; // bit depth
|
|
ihdr[9] = 2; // color type: truecolor
|
|
ihdr[10] = 0; // compression
|
|
ihdr[11] = 0; // filter
|
|
ihdr[12] = 0; // interlace
|
|
|
|
const rows = [];
|
|
for (let y = 0; y < height; y++) {
|
|
const row = Buffer.alloc(1 + width * 3);
|
|
row[0] = 0; // filter: none
|
|
for (let x = 0; x < width; x++) {
|
|
const i = 1 + x * 3;
|
|
const left = x < width / 2;
|
|
row[i + 0] = left ? 255 : 0;
|
|
row[i + 1] = left ? 0 : 255;
|
|
row[i + 2] = 0;
|
|
}
|
|
rows.push(row);
|
|
}
|
|
const raw = Buffer.concat(rows);
|
|
const idat = zlib.deflateSync(raw, { level: 9 });
|
|
|
|
const png = Buffer.concat([
|
|
sig,
|
|
chunk("IHDR", ihdr),
|
|
chunk("IDAT", idat),
|
|
chunk("IEND", Buffer.alloc(0)),
|
|
]);
|
|
fs.writeFileSync(out, png);
|
|
NODE
|
|
}
|
|
|
|
run_agent_turn() {
|
|
local profile="$1"
|
|
local session_id="$2"
|
|
local prompt="$3"
|
|
local out_json="$4"
|
|
clawdbot --profile "$profile" agent \
|
|
--session-id "$session_id" \
|
|
--message "$prompt" \
|
|
--thinking off \
|
|
--json >"$out_json"
|
|
}
|
|
|
|
assert_agent_json_has_text() {
|
|
local path="$1"
|
|
node - <<'NODE' "$path"
|
|
const fs = require("node:fs");
|
|
const p = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const payloads =
|
|
Array.isArray(p?.result?.payloads) ? p.result.payloads :
|
|
Array.isArray(p?.payloads) ? p.payloads :
|
|
[];
|
|
const texts = payloads.map((x) => String(x?.text ?? "").trim()).filter(Boolean);
|
|
if (texts.length === 0) process.exit(1);
|
|
NODE
|
|
}
|
|
|
|
assert_agent_json_ok() {
|
|
local json_path="$1"
|
|
local expect_provider="$2"
|
|
node - <<'NODE' "$json_path" "$expect_provider"
|
|
const fs = require("node:fs");
|
|
const jsonPath = process.argv[2];
|
|
const expectProvider = process.argv[3];
|
|
const p = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
|
|
|
if (typeof p?.status === "string" && p.status !== "ok" && p.status !== "accepted") {
|
|
console.error(`ERROR: gateway status=${p.status}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const result = p?.result ?? p;
|
|
const payloads = Array.isArray(result?.payloads) ? result.payloads : [];
|
|
const anyError = payloads.some((pl) => pl && pl.isError === true);
|
|
const combinedText = payloads.map((pl) => String(pl?.text ?? "")).filter(Boolean).join("\n").trim();
|
|
if (anyError) {
|
|
console.error(`ERROR: agent returned error payload: ${combinedText}`);
|
|
process.exit(1);
|
|
}
|
|
if (/rate_limit_error/i.test(combinedText) || /^429\\b/.test(combinedText)) {
|
|
console.error(`ERROR: agent rate limited: ${combinedText}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const meta = result?.meta;
|
|
const provider =
|
|
(typeof meta?.agentMeta?.provider === "string" && meta.agentMeta.provider.trim()) ||
|
|
(typeof meta?.provider === "string" && meta.provider.trim()) ||
|
|
"";
|
|
if (expectProvider && provider && provider !== expectProvider) {
|
|
console.error(`ERROR: expected provider=${expectProvider}, got provider=${provider}`);
|
|
process.exit(1);
|
|
}
|
|
NODE
|
|
}
|
|
|
|
extract_first_text() {
|
|
local path="$1"
|
|
node - <<'NODE' "$path"
|
|
const fs = require("node:fs");
|
|
const p = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const payloads =
|
|
Array.isArray(p?.result?.payloads) ? p.result.payloads :
|
|
Array.isArray(p?.payloads) ? p.payloads :
|
|
[];
|
|
const text = payloads.map((x) => String(x?.text ?? "").trim()).filter(Boolean)[0] ?? "";
|
|
process.stdout.write(text);
|
|
NODE
|
|
}
|
|
|
|
assert_session_used_tools() {
|
|
local jsonl="$1"
|
|
shift
|
|
node - <<'NODE' "$jsonl" "$@"
|
|
const fs = require("node:fs");
|
|
const jsonl = process.argv[2];
|
|
const required = new Set(process.argv.slice(3));
|
|
|
|
const raw = fs.readFileSync(jsonl, "utf8");
|
|
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
const seen = new Set();
|
|
|
|
const toolTypes = new Set([
|
|
"tool_use",
|
|
"tool_result",
|
|
"tool",
|
|
"tool-call",
|
|
"tool_call",
|
|
"tooluse",
|
|
"tool-use",
|
|
"toolresult",
|
|
"tool-result",
|
|
]);
|
|
function walk(node, parent) {
|
|
if (!node) return;
|
|
if (Array.isArray(node)) {
|
|
for (const item of node) walk(item, node);
|
|
return;
|
|
}
|
|
if (typeof node !== "object") return;
|
|
const obj = node;
|
|
const t = typeof obj.type === "string" ? obj.type : null;
|
|
if (t && (toolTypes.has(t) || /tool/i.test(t))) {
|
|
const name =
|
|
typeof obj.name === "string" ? obj.name :
|
|
typeof obj.toolName === "string" ? obj.toolName :
|
|
typeof obj.tool_name === "string" ? obj.tool_name :
|
|
(obj.tool && typeof obj.tool.name === "string") ? obj.tool.name :
|
|
null;
|
|
if (name) seen.add(name);
|
|
}
|
|
if (typeof obj.name === "string" && typeof obj.input === "object" && obj.input) {
|
|
// Many tool-use blocks look like { type: "...", name: "exec", input: {...} }
|
|
// but some transcripts omit/rename type.
|
|
seen.add(obj.name);
|
|
}
|
|
// OpenAI-ish tool call shapes.
|
|
if (Array.isArray(obj.tool_calls)) {
|
|
for (const c of obj.tool_calls) {
|
|
const fn = c?.function;
|
|
if (fn && typeof fn.name === "string") seen.add(fn.name);
|
|
}
|
|
}
|
|
if (obj.function && typeof obj.function.name === "string") seen.add(obj.function.name);
|
|
for (const v of Object.values(obj)) walk(v, obj);
|
|
}
|
|
|
|
for (const line of lines) {
|
|
try {
|
|
const entry = JSON.parse(line);
|
|
walk(entry, null);
|
|
} catch {
|
|
// ignore unparsable lines
|
|
}
|
|
}
|
|
|
|
const missing = [...required].filter((t) => !seen.has(t));
|
|
if (missing.length > 0) {
|
|
console.error(`Missing tools in transcript: ${missing.join(", ")}`);
|
|
console.error(`Seen tools: ${[...seen].sort().join(", ")}`);
|
|
console.error("Transcript head:");
|
|
console.error(lines.slice(0, 5).join("\n"));
|
|
process.exit(1);
|
|
}
|
|
NODE
|
|
}
|
|
|
|
run_profile() {
|
|
local profile="$1"
|
|
local port="$2"
|
|
local workspace="$3"
|
|
local agent_model_provider="$4" # "openai"|"anthropic"
|
|
|
|
echo "==> Onboard ($profile)"
|
|
if [[ "$agent_model_provider" == "openai" ]]; then
|
|
clawdbot --profile "$profile" onboard \
|
|
--non-interactive \
|
|
--flow quickstart \
|
|
--auth-choice openai-api-key \
|
|
--openai-api-key "$OPENAI_API_KEY" \
|
|
--gateway-port "$port" \
|
|
--gateway-bind loopback \
|
|
--gateway-auth token \
|
|
--workspace "$workspace" \
|
|
--skip-health
|
|
elif [[ -n "$ANTHROPIC_API_TOKEN" ]]; then
|
|
clawdbot --profile "$profile" onboard \
|
|
--non-interactive \
|
|
--flow quickstart \
|
|
--auth-choice token \
|
|
--token-provider anthropic \
|
|
--token "$ANTHROPIC_API_TOKEN" \
|
|
--gateway-port "$port" \
|
|
--gateway-bind loopback \
|
|
--gateway-auth token \
|
|
--workspace "$workspace" \
|
|
--skip-health
|
|
else
|
|
clawdbot --profile "$profile" onboard \
|
|
--non-interactive \
|
|
--flow quickstart \
|
|
--auth-choice apiKey \
|
|
--anthropic-api-key "$ANTHROPIC_API_KEY" \
|
|
--gateway-port "$port" \
|
|
--gateway-bind loopback \
|
|
--gateway-auth token \
|
|
--workspace "$workspace" \
|
|
--skip-health
|
|
fi
|
|
|
|
echo "==> Verify workspace identity files ($profile)"
|
|
test -f "$workspace/AGENTS.md"
|
|
test -f "$workspace/IDENTITY.md"
|
|
test -f "$workspace/USER.md"
|
|
test -f "$workspace/SOUL.md"
|
|
test -f "$workspace/TOOLS.md"
|
|
|
|
echo "==> Configure models ($profile)"
|
|
local agent_model
|
|
local image_model
|
|
if [[ "$agent_model_provider" == "openai" ]]; then
|
|
agent_model="$(set_agent_model "$profile" \
|
|
"openai/gpt-4.1-mini" \
|
|
"openai/gpt-4.1" \
|
|
"openai/gpt-4o-mini" \
|
|
"openai/gpt-4o")"
|
|
image_model="$(set_image_model "$profile" \
|
|
"openai/gpt-4.1" \
|
|
"openai/gpt-4o-mini" \
|
|
"openai/gpt-4o" \
|
|
"openai/gpt-4.1-mini")"
|
|
else
|
|
agent_model="$(set_agent_model "$profile" \
|
|
"anthropic/claude-opus-4-5" \
|
|
"claude-opus-4-5")"
|
|
image_model="$(set_image_model "$profile" \
|
|
"anthropic/claude-opus-4-5" \
|
|
"claude-opus-4-5")"
|
|
fi
|
|
echo "model=$agent_model"
|
|
echo "imageModel=$image_model"
|
|
|
|
echo "==> Prepare tool fixtures ($profile)"
|
|
PROOF_TXT="$workspace/proof.txt"
|
|
PROOF_COPY="$workspace/copy.txt"
|
|
HOSTNAME_TXT="$workspace/hostname.txt"
|
|
IMAGE_PNG="$workspace/proof.png"
|
|
IMAGE_TXT="$workspace/image.txt"
|
|
SESSION_ID="e2e-tools-${profile}"
|
|
SESSION_JSONL="/root/.clawdbot-${profile}/agents/main/sessions/${SESSION_ID}.jsonl"
|
|
|
|
PROOF_VALUE="$(node -e 'console.log(require("node:crypto").randomBytes(16).toString("hex"))')"
|
|
echo -n "$PROOF_VALUE" >"$PROOF_TXT"
|
|
write_png_lr_rg "$IMAGE_PNG"
|
|
EXPECTED_HOSTNAME="$(cat /etc/hostname | tr -d '\r\n')"
|
|
|
|
echo "==> Start gateway ($profile)"
|
|
GATEWAY_LOG="$workspace/gateway.log"
|
|
clawdbot --profile "$profile" gateway --port "$port" --bind loopback >"$GATEWAY_LOG" 2>&1 &
|
|
GATEWAY_PID="$!"
|
|
cleanup_profile() {
|
|
if kill -0 "$GATEWAY_PID" 2>/dev/null; then
|
|
kill "$GATEWAY_PID" 2>/dev/null || true
|
|
wait "$GATEWAY_PID" 2>/dev/null || true
|
|
fi
|
|
}
|
|
trap cleanup_profile EXIT
|
|
|
|
echo "==> Wait for health ($profile)"
|
|
for _ in $(seq 1 60); do
|
|
if clawdbot --profile "$profile" health --timeout 2000 --json >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 0.25
|
|
done
|
|
clawdbot --profile "$profile" health --timeout 10000 --json >/dev/null
|
|
|
|
echo "==> Agent turns ($profile)"
|
|
TURN1_JSON="/tmp/agent-${profile}-1.json"
|
|
TURN2_JSON="/tmp/agent-${profile}-2.json"
|
|
TURN3_JSON="/tmp/agent-${profile}-3.json"
|
|
TURN4_JSON="/tmp/agent-${profile}-4.json"
|
|
|
|
run_agent_turn "$profile" "$SESSION_ID" \
|
|
"Use the read tool (not exec) to read proof.txt. Reply with the exact contents only (no extra whitespace)." \
|
|
"$TURN1_JSON"
|
|
assert_agent_json_has_text "$TURN1_JSON"
|
|
assert_agent_json_ok "$TURN1_JSON" "$agent_model_provider"
|
|
local reply1
|
|
reply1="$(extract_first_text "$TURN1_JSON" | tr -d '\r\n')"
|
|
if [[ "$reply1" != "$PROOF_VALUE" ]]; then
|
|
echo "ERROR: agent did not read proof.txt correctly ($profile): $reply1" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local prompt2
|
|
prompt2=$'Use the write tool (not exec) to write exactly this string into copy.txt:\n'"${reply1}"$'\nThen use the read tool (not exec) to read copy.txt and reply with the exact contents only (no extra whitespace).'
|
|
run_agent_turn "$profile" "$SESSION_ID" "$prompt2" "$TURN2_JSON"
|
|
assert_agent_json_has_text "$TURN2_JSON"
|
|
assert_agent_json_ok "$TURN2_JSON" "$agent_model_provider"
|
|
local copy_value
|
|
copy_value="$(cat "$PROOF_COPY" 2>/dev/null | tr -d '\r\n' || true)"
|
|
if [[ "$copy_value" != "$PROOF_VALUE" ]]; then
|
|
echo "ERROR: copy.txt did not match proof.txt ($profile)" >&2
|
|
exit 1
|
|
fi
|
|
local reply2
|
|
reply2="$(extract_first_text "$TURN2_JSON" | tr -d '\r\n')"
|
|
if [[ "$reply2" != "$PROOF_VALUE" ]]; then
|
|
echo "ERROR: agent did not read copy.txt correctly ($profile): $reply2" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local prompt3
|
|
prompt3=$'Use the exec tool to run: cat /etc/hostname\nThen use the write tool to write the exact stdout (trim trailing newline) into hostname.txt. Reply with the hostname only.'
|
|
run_agent_turn "$profile" "$SESSION_ID" "$prompt3" "$TURN3_JSON"
|
|
assert_agent_json_has_text "$TURN3_JSON"
|
|
assert_agent_json_ok "$TURN3_JSON" "$agent_model_provider"
|
|
if [[ "$(cat "$HOSTNAME_TXT" 2>/dev/null | tr -d '\r\n' || true)" != "$EXPECTED_HOSTNAME" ]]; then
|
|
echo "ERROR: hostname.txt did not match /etc/hostname ($profile)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_agent_turn "$profile" "$SESSION_ID" \
|
|
"Use the image tool on proof.png. Determine which color is on the left half and which is on the right half. Then use the write tool to write exactly: LEFT=RED RIGHT=GREEN into image.txt. Reply with exactly: LEFT=RED RIGHT=GREEN" \
|
|
"$TURN4_JSON"
|
|
assert_agent_json_has_text "$TURN4_JSON"
|
|
assert_agent_json_ok "$TURN4_JSON" "$agent_model_provider"
|
|
if [[ "$(cat "$IMAGE_TXT" 2>/dev/null | tr -d '\r\n' || true)" != "LEFT=RED RIGHT=GREEN" ]]; then
|
|
echo "ERROR: image.txt did not contain expected marker ($profile)" >&2
|
|
exit 1
|
|
fi
|
|
local reply4
|
|
reply4="$(extract_first_text "$TURN4_JSON")"
|
|
if [[ "$reply4" != "LEFT=RED RIGHT=GREEN" ]]; then
|
|
echo "ERROR: agent reply did not contain expected marker ($profile): $reply4" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Verify tool usage via session transcript ($profile)"
|
|
# Give the gateway a moment to flush transcripts.
|
|
sleep 1
|
|
if [[ ! -f "$SESSION_JSONL" ]]; then
|
|
echo "ERROR: missing session transcript ($profile): $SESSION_JSONL" >&2
|
|
ls -la "/root/.clawdbot-${profile}/agents/main/sessions" >&2 || true
|
|
exit 1
|
|
fi
|
|
assert_session_used_tools "$SESSION_JSONL" read write exec image
|
|
|
|
cleanup_profile
|
|
trap - EXIT
|
|
}
|
|
|
|
if [[ "$MODELS_MODE" == "openai" || "$MODELS_MODE" == "both" ]]; then
|
|
run_profile "e2e-openai" "18789" "/tmp/clawd-e2e-openai" "openai"
|
|
fi
|
|
|
|
if [[ "$MODELS_MODE" == "anthropic" || "$MODELS_MODE" == "both" ]]; then
|
|
run_profile "e2e-anthropic" "18799" "/tmp/clawd-e2e-anthropic" "anthropic"
|
|
fi
|
|
|
|
echo "OK"
|