mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 11:40:39 +09:00
123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
|
|
import type {
|
||
|
|
AdapterEnvironmentCheck,
|
||
|
|
AdapterEnvironmentTestContext,
|
||
|
|
AdapterEnvironmentTestResult,
|
||
|
|
} from "@paperclip/adapter-utils";
|
||
|
|
import { asString, parseObject } from "@paperclip/adapter-utils/server-utils";
|
||
|
|
|
||
|
|
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||
|
|
if (checks.some((check) => check.level === "error")) return "fail";
|
||
|
|
if (checks.some((check) => check.level === "warn")) return "warn";
|
||
|
|
return "pass";
|
||
|
|
}
|
||
|
|
|
||
|
|
function isLoopbackHost(hostname: string): boolean {
|
||
|
|
const value = hostname.trim().toLowerCase();
|
||
|
|
return value === "localhost" || value === "127.0.0.1" || value === "::1";
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function testEnvironment(
|
||
|
|
ctx: AdapterEnvironmentTestContext,
|
||
|
|
): Promise<AdapterEnvironmentTestResult> {
|
||
|
|
const checks: AdapterEnvironmentCheck[] = [];
|
||
|
|
const config = parseObject(ctx.config);
|
||
|
|
const urlValue = asString(config.url, "");
|
||
|
|
|
||
|
|
if (!urlValue) {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_url_missing",
|
||
|
|
level: "error",
|
||
|
|
message: "OpenClaw adapter requires a webhook URL.",
|
||
|
|
hint: "Set adapterConfig.url to your OpenClaw webhook endpoint.",
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
adapterType: ctx.adapterType,
|
||
|
|
status: summarizeStatus(checks),
|
||
|
|
checks,
|
||
|
|
testedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
let url: URL | null = null;
|
||
|
|
try {
|
||
|
|
url = new URL(urlValue);
|
||
|
|
} catch {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_url_invalid",
|
||
|
|
level: "error",
|
||
|
|
message: `Invalid URL: ${urlValue}`,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (url && url.protocol !== "http:" && url.protocol !== "https:") {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_url_protocol_invalid",
|
||
|
|
level: "error",
|
||
|
|
message: `Unsupported URL protocol: ${url.protocol}`,
|
||
|
|
hint: "Use an http:// or https:// endpoint.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (url) {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_url_valid",
|
||
|
|
level: "info",
|
||
|
|
message: `Configured endpoint: ${url.toString()}`,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (isLoopbackHost(url.hostname)) {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_loopback_endpoint",
|
||
|
|
level: "warn",
|
||
|
|
message: "Endpoint uses loopback hostname. Remote OpenClaw workers cannot reach localhost on the Paperclip host.",
|
||
|
|
hint: "Use a reachable hostname/IP (for example Tailscale/private hostname or public domain).",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const method = asString(config.method, "POST").trim().toUpperCase() || "POST";
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_method_configured",
|
||
|
|
level: "info",
|
||
|
|
message: `Configured method: ${method}`,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (url && (url.protocol === "http:" || url.protocol === "https:")) {
|
||
|
|
const controller = new AbortController();
|
||
|
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
||
|
|
try {
|
||
|
|
const response = await fetch(url, { method: "HEAD", signal: controller.signal });
|
||
|
|
if (!response.ok && response.status !== 405 && response.status !== 501) {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_endpoint_probe_unexpected_status",
|
||
|
|
level: "warn",
|
||
|
|
message: `Endpoint probe returned HTTP ${response.status}.`,
|
||
|
|
hint: "Verify OpenClaw webhook reachability and auth/network settings.",
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_endpoint_probe_ok",
|
||
|
|
level: "info",
|
||
|
|
message: "Endpoint responded to a HEAD probe.",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
checks.push({
|
||
|
|
code: "openclaw_endpoint_probe_failed",
|
||
|
|
level: "warn",
|
||
|
|
message: err instanceof Error ? err.message : "Endpoint probe failed",
|
||
|
|
hint: "This may be expected in restricted networks; validate from the Paperclip server host.",
|
||
|
|
});
|
||
|
|
} finally {
|
||
|
|
clearTimeout(timeout);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
adapterType: ctx.adapterType,
|
||
|
|
status: summarizeStatus(checks),
|
||
|
|
checks,
|
||
|
|
testedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
}
|