320 lines · 9.9 KB · TypeScript
RawDownload
/** * ProviderHealthLive - Startup-time provider health checks. * * Performs one-time provider readiness probes when the server starts and * keeps the resulting snapshot in memory for `server.getConfig`. * * Uses effect's ChildProcessSpawner to run CLI probes natively. * * @module ProviderHealthLive */import type {  ServerProviderAuthStatus,  ServerProviderStatus,  ServerProviderStatusState,} from "@t3tools/contracts";import { Effect, Layer, Option, Result, Stream } from "effect";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import {  formatCodexCliUpgradeMessage,  isCodexCliVersionSupported,  parseCodexCliVersion,} from "../codexCliVersion";import { ProviderHealth, type ProviderHealthShape } from "../Services/ProviderHealth"; const DEFAULT_TIMEOUT_MS = 4_000;const CODEX_PROVIDER = "codex" as const; // ── Pure helpers ──────────────────────────────────────────────────── export interface CommandResult {  readonly stdout: string;  readonly stderr: string;  readonly code: number;} function nonEmptyTrimmed(value: string | undefined): string | undefined {  if (!value) return undefined;  const trimmed = value.trim();  return trimmed.length > 0 ? trimmed : undefined;} function isCommandMissingCause(error: unknown): boolean {  if (!(error instanceof Error)) return false;  const lower = error.message.toLowerCase();  return (    lower.includes("command not found: codex") ||    lower.includes("spawn codex enoent") ||    lower.includes("enoent") ||    lower.includes("notfound")  );} function detailFromResult(  result: CommandResult & { readonly timedOut?: boolean },): string | undefined {  if (result.timedOut) return "Timed out while running command.";  const stderr = nonEmptyTrimmed(result.stderr);  if (stderr) return stderr;  const stdout = nonEmptyTrimmed(result.stdout);  if (stdout) return stdout;  if (result.code !== 0) {    return `Command exited with code ${result.code}.`;  }  return undefined;} function extractAuthBoolean(value: unknown): boolean | undefined {  if (Array.isArray(value)) {    for (const entry of value) {      const nested = extractAuthBoolean(entry);      if (nested !== undefined) return nested;    }    return undefined;  }   if (!value || typeof value !== "object") return undefined;   const record = value as Record<string, unknown>;  for (const key of ["authenticated", "isAuthenticated", "loggedIn", "isLoggedIn"] as const) {    if (typeof record[key] === "boolean") return record[key];  }  for (const key of ["auth", "status", "session", "account"] as const) {    const nested = extractAuthBoolean(record[key]);    if (nested !== undefined) return nested;  }  return undefined;} export function parseAuthStatusFromOutput(result: CommandResult): {  readonly status: ServerProviderStatusState;  readonly authStatus: ServerProviderAuthStatus;  readonly message?: string;} {  const lowerOutput = `${result.stdout}\n${result.stderr}`.toLowerCase();   if (    lowerOutput.includes("unknown command") ||    lowerOutput.includes("unrecognized command") ||    lowerOutput.includes("unexpected argument")  ) {    return {      status: "warning",      authStatus: "unknown",      message: "Codex CLI authentication status command is unavailable in this Codex version.",    };  }   if (    lowerOutput.includes("not logged in") ||    lowerOutput.includes("login required") ||    lowerOutput.includes("authentication required") ||    lowerOutput.includes("run `codex login`") ||    lowerOutput.includes("run codex login")  ) {    return {      status: "error",      authStatus: "unauthenticated",      message: "Codex CLI is not authenticated. Run `codex login` and try again.",    };  }   const parsedAuth = (() => {    const trimmed = result.stdout.trim();    if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {      return { attemptedJsonParse: false as const, auth: undefined as boolean | undefined };    }    try {      return {        attemptedJsonParse: true as const,        auth: extractAuthBoolean(JSON.parse(trimmed)),      };    } catch {      return { attemptedJsonParse: false as const, auth: undefined as boolean | undefined };    }  })();   if (parsedAuth.auth === true) {    return { status: "ready", authStatus: "authenticated" };  }  if (parsedAuth.auth === false) {    return {      status: "error",      authStatus: "unauthenticated",      message: "Codex CLI is not authenticated. Run `codex login` and try again.",    };  }  if (parsedAuth.attemptedJsonParse) {    return {      status: "warning",      authStatus: "unknown",      message:        "Could not verify Codex authentication status from JSON output (missing auth marker).",    };  }  if (result.code === 0) {    return { status: "ready", authStatus: "authenticated" };  }   const detail = detailFromResult(result);  return {    status: "warning",    authStatus: "unknown",    message: detail      ? `Could not verify Codex authentication status. ${detail}`      : "Could not verify Codex authentication status.",  };} // ── Effect-native command execution ───────────────────────────────── const collectStreamAsString = <E>(stream: Stream.Stream<Uint8Array, E>): Effect.Effect<string, E> =>  Stream.runFold(    stream,    () => "",    (acc, chunk) => acc + new TextDecoder().decode(chunk),  ); const runCodexCommand = (args: ReadonlyArray<string>) =>  Effect.gen(function* () {    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;    const command = ChildProcess.make("codex", [...args], {      shell: process.platform === "win32",    });     const child = yield* spawner.spawn(command);     const [stdout, stderr, exitCode] = yield* Effect.all(      [        collectStreamAsString(child.stdout),        collectStreamAsString(child.stderr),        child.exitCode.pipe(Effect.map(Number)),      ],      { concurrency: "unbounded" },    );     return { stdout, stderr, code: exitCode } satisfies CommandResult;  }).pipe(Effect.scoped); // ── Health check ──────────────────────────────────────────────────── export const checkCodexProviderStatus: Effect.Effect<  ServerProviderStatus,  never,  ChildProcessSpawner.ChildProcessSpawner> = Effect.gen(function* () {  const checkedAt = new Date().toISOString();   // Probe 1: `codex --version` — is the CLI reachable?  const versionProbe = yield* runCodexCommand(["--version"]).pipe(    Effect.timeoutOption(DEFAULT_TIMEOUT_MS),    Effect.result,  );   if (Result.isFailure(versionProbe)) {    const error = versionProbe.failure;    return {      provider: CODEX_PROVIDER,      status: "error" as const,      available: false,      authStatus: "unknown" as const,      checkedAt,      message: isCommandMissingCause(error)        ? "Codex CLI (`codex`) is not installed or not on PATH."        : `Failed to execute Codex CLI health check: ${error instanceof Error ? error.message : String(error)}.`,    };  }   if (Option.isNone(versionProbe.success)) {    return {      provider: CODEX_PROVIDER,      status: "error" as const,      available: false,      authStatus: "unknown" as const,      checkedAt,      message: "Codex CLI is installed but failed to run. Timed out while running command.",    };  }   const version = versionProbe.success.value;  if (version.code !== 0) {    const detail = detailFromResult(version);    return {      provider: CODEX_PROVIDER,      status: "error" as const,      available: false,      authStatus: "unknown" as const,      checkedAt,      message: detail        ? `Codex CLI is installed but failed to run. ${detail}`        : "Codex CLI is installed but failed to run.",    };  }   const parsedVersion = parseCodexCliVersion(`${version.stdout}\n${version.stderr}`);  if (parsedVersion && !isCodexCliVersionSupported(parsedVersion)) {    return {      provider: CODEX_PROVIDER,      status: "error" as const,      available: false,      authStatus: "unknown" as const,      checkedAt,      message: formatCodexCliUpgradeMessage(parsedVersion),    };  }   // Probe 2: `codex login status` — is the user authenticated?  const authProbe = yield* runCodexCommand(["login", "status"]).pipe(    Effect.timeoutOption(DEFAULT_TIMEOUT_MS),    Effect.result,  );   if (Result.isFailure(authProbe)) {    const error = authProbe.failure;    return {      provider: CODEX_PROVIDER,      status: "warning" as const,      available: true,      authStatus: "unknown" as const,      checkedAt,      message:        error instanceof Error          ? `Could not verify Codex authentication status: ${error.message}.`          : "Could not verify Codex authentication status.",    };  }   if (Option.isNone(authProbe.success)) {    return {      provider: CODEX_PROVIDER,      status: "warning" as const,      available: true,      authStatus: "unknown" as const,      checkedAt,      message: "Could not verify Codex authentication status. Timed out while running command.",    };  }   const parsed = parseAuthStatusFromOutput(authProbe.success.value);  return {    provider: CODEX_PROVIDER,    status: parsed.status,    available: true,    authStatus: parsed.authStatus,    checkedAt,    ...(parsed.message ? { message: parsed.message } : {}),  } satisfies ServerProviderStatus;}); // ── Layer ─────────────────────────────────────────────────────────── export const ProviderHealthLive = Layer.effect(  ProviderHealth,  Effect.gen(function* () {    const codexStatus = yield* checkCodexProviderStatus;    return {      getStatuses: Effect.succeed([codexStatus]),    } satisfies ProviderHealthShape;  }),);