Introduce testEnvironment() on ServerAdapterModule with structured pass/warn/fail diagnostics for all four adapter types (claude_local, codex_local, process, http). Adds POST test-environment endpoint, shared types/validators, adapter test implementations, and UI API client. Includes asset type foundations used by related features. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
import type {
|
|
AdapterEnvironmentCheck,
|
|
AdapterEnvironmentTestContext,
|
|
AdapterEnvironmentTestResult,
|
|
} from "../types.js";
|
|
import { asString, parseObject } from "../utils.js";
|
|
|
|
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 normalizeMethod(input: string): string {
|
|
const trimmed = input.trim();
|
|
return trimmed.length > 0 ? trimmed.toUpperCase() : "POST";
|
|
}
|
|
|
|
export async function testEnvironment(
|
|
ctx: AdapterEnvironmentTestContext,
|
|
): Promise<AdapterEnvironmentTestResult> {
|
|
const checks: AdapterEnvironmentCheck[] = [];
|
|
const config = parseObject(ctx.config);
|
|
const urlValue = asString(config.url, "");
|
|
const method = normalizeMethod(asString(config.method, "POST"));
|
|
|
|
if (!urlValue) {
|
|
checks.push({
|
|
code: "http_url_missing",
|
|
level: "error",
|
|
message: "HTTP adapter requires a URL.",
|
|
hint: "Set adapterConfig.url to an absolute http(s) 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: "http_url_invalid",
|
|
level: "error",
|
|
message: `Invalid URL: ${urlValue}`,
|
|
});
|
|
}
|
|
|
|
if (url && url.protocol !== "http:" && url.protocol !== "https:") {
|
|
checks.push({
|
|
code: "http_url_protocol_invalid",
|
|
level: "error",
|
|
message: `Unsupported URL protocol: ${url.protocol}`,
|
|
hint: "Use an http:// or https:// endpoint.",
|
|
});
|
|
}
|
|
|
|
if (url) {
|
|
checks.push({
|
|
code: "http_url_valid",
|
|
level: "info",
|
|
message: `Configured endpoint: ${url.toString()}`,
|
|
});
|
|
}
|
|
|
|
checks.push({
|
|
code: "http_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: "http_endpoint_probe_unexpected_status",
|
|
level: "warn",
|
|
message: `Endpoint probe returned HTTP ${response.status}.`,
|
|
hint: "Verify the endpoint is reachable from the Paperclip server host.",
|
|
});
|
|
} else {
|
|
checks.push({
|
|
code: "http_endpoint_probe_ok",
|
|
level: "info",
|
|
message: "Endpoint responded to a HEAD probe.",
|
|
});
|
|
}
|
|
} catch (err) {
|
|
checks.push({
|
|
code: "http_endpoint_probe_failed",
|
|
level: "warn",
|
|
message: err instanceof Error ? err.message : "Endpoint probe failed",
|
|
hint: "This may be expected in restricted networks; verify connectivity when invoking runs.",
|
|
});
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
return {
|
|
adapterType: ctx.adapterType,
|
|
status: summarizeStatus(checks),
|
|
checks,
|
|
testedAt: new Date().toISOString(),
|
|
};
|
|
}
|