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>
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
import type {
|
|
AdapterEnvironmentCheck,
|
|
AdapterEnvironmentTestContext,
|
|
AdapterEnvironmentTestResult,
|
|
} from "../types.js";
|
|
import {
|
|
asString,
|
|
parseObject,
|
|
ensureAbsoluteDirectory,
|
|
ensureCommandResolvable,
|
|
ensurePathInEnv,
|
|
} 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";
|
|
}
|
|
|
|
export async function testEnvironment(
|
|
ctx: AdapterEnvironmentTestContext,
|
|
): Promise<AdapterEnvironmentTestResult> {
|
|
const checks: AdapterEnvironmentCheck[] = [];
|
|
const config = parseObject(ctx.config);
|
|
const command = asString(config.command, "");
|
|
const cwd = asString(config.cwd, process.cwd());
|
|
|
|
if (!command) {
|
|
checks.push({
|
|
code: "process_command_missing",
|
|
level: "error",
|
|
message: "Process adapter requires a command.",
|
|
hint: "Set adapterConfig.command to an executable command.",
|
|
});
|
|
} else {
|
|
checks.push({
|
|
code: "process_command_present",
|
|
level: "info",
|
|
message: `Configured command: ${command}`,
|
|
});
|
|
}
|
|
|
|
try {
|
|
await ensureAbsoluteDirectory(cwd);
|
|
checks.push({
|
|
code: "process_cwd_valid",
|
|
level: "info",
|
|
message: `Working directory is valid: ${cwd}`,
|
|
});
|
|
} catch (err) {
|
|
checks.push({
|
|
code: "process_cwd_invalid",
|
|
level: "error",
|
|
message: err instanceof Error ? err.message : "Invalid working directory",
|
|
detail: cwd,
|
|
});
|
|
}
|
|
|
|
if (command) {
|
|
const envConfig = parseObject(config.env);
|
|
const env: Record<string, string> = {};
|
|
for (const [key, value] of Object.entries(envConfig)) {
|
|
if (typeof value === "string") env[key] = value;
|
|
}
|
|
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
|
try {
|
|
await ensureCommandResolvable(command, cwd, runtimeEnv);
|
|
checks.push({
|
|
code: "process_command_resolvable",
|
|
level: "info",
|
|
message: `Command is executable: ${command}`,
|
|
});
|
|
} catch (err) {
|
|
checks.push({
|
|
code: "process_command_unresolvable",
|
|
level: "error",
|
|
message: err instanceof Error ? err.message : "Command is not executable",
|
|
detail: command,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
adapterType: ctx.adapterType,
|
|
status: summarizeStatus(checks),
|
|
checks,
|
|
testedAt: new Date().toISOString(),
|
|
};
|
|
}
|