342 lines · 10.1 KB · TypeScript
RawDownload
import type { OpenCodeSettings, ServerProvider } from "@t3tools/contracts";import { Cause, Effect, Equal, Layer, Stream } from "effect"; import { ServerConfig } from "../../config.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";import {  buildServerProvider,  isCommandMissingCause,  parseGenericCliVersion,  providerModelsFromSettings,} from "../providerSnapshot.ts";import { OpenCodeProvider } from "../Services/OpenCodeProvider.ts";import {  connectToOpenCodeServer,  DEFAULT_OPENCODE_MODEL_CAPABILITIES,  createOpenCodeSdkClient,  flattenOpenCodeModels,  loadOpenCodeInventory,  runOpenCodeCommand,} from "../opencodeRuntime.ts"; const PROVIDER = "opencode" as const; class OpenCodeProbePromiseError extends Error {  override readonly cause: unknown;   constructor(cause: unknown) {    super(cause instanceof Error ? cause.message : String(cause));    this.cause = cause;    this.name = "OpenCodeProbePromiseError";  }} function toOpenCodeProbeError(cause: unknown): OpenCodeProbePromiseError {  return new OpenCodeProbePromiseError(cause);} function normalizedErrorMessage(cause: unknown): string | undefined {  if (!(cause instanceof Error)) {    return undefined;  }   const message = cause.message.trim();  if (message.length === 0) {    return undefined;  }  if (    message === "An error occurred in Effect.tryPromise" ||    message === "An error occurred in Effect.try"  ) {    return undefined;  }  return message;} function formatOpenCodeProbeError(input: {  readonly cause: unknown;  readonly isExternalServer: boolean;  readonly serverUrl: string;}): { readonly installed: boolean; readonly message: string } {  const lower = input.cause instanceof Error ? input.cause.message.toLowerCase() : "";  const detail = normalizedErrorMessage(input.cause);   if (input.isExternalServer) {    if (      lower.includes("401") ||      lower.includes("403") ||      lower.includes("unauthorized") ||      lower.includes("forbidden")    ) {      return {        installed: true,        message: "OpenCode server rejected authentication. Check the server URL and password.",      };    }     if (      lower.includes("econnrefused") ||      lower.includes("enotfound") ||      lower.includes("fetch failed") ||      lower.includes("networkerror") ||      lower.includes("timed out") ||      lower.includes("timeout") ||      lower.includes("socket hang up")    ) {      return {        installed: true,        message: `Couldn't reach the configured OpenCode server at ${input.serverUrl}. Check that the server is running and the URL is correct.`,      };    }     return {      installed: true,      message: detail ?? "Failed to connect to the configured OpenCode server.",    };  }   if (input.cause instanceof Error && isCommandMissingCause(input.cause)) {    return {      installed: false,      message: "OpenCode CLI (`opencode`) is not installed or not on PATH.",    };  }   if (lower.includes("quarantine")) {    return {      installed: true,      message:        "macOS is blocking the OpenCode binary (quarantine). Run `xattr -d com.apple.quarantine $(which opencode)` to fix this.",    };  }   if (lower.includes("invalid code signature") || lower.includes("corrupted")) {    return {      installed: true,      message:        "macOS killed the OpenCode process due to an invalid code signature. The binary may be corrupted — try reinstalling OpenCode.",    };  }   return {    installed: true,    message: detail      ? `Failed to execute OpenCode CLI health check: ${detail}`      : "Failed to execute OpenCode CLI health check.",  };} const makePendingOpenCodeProvider = (openCodeSettings: OpenCodeSettings): ServerProvider => {  const checkedAt = new Date().toISOString();  const models = providerModelsFromSettings(    [],    PROVIDER,    openCodeSettings.customModels,    DEFAULT_OPENCODE_MODEL_CAPABILITIES,  );   if (!openCodeSettings.enabled) {    return buildServerProvider({      provider: PROVIDER,      enabled: false,      checkedAt,      models,      probe: {        installed: false,        version: null,        status: "warning",        auth: { status: "unknown" },        message:          openCodeSettings.serverUrl.trim().length > 0            ? "OpenCode is disabled in T3 Code settings. A server URL is configured."            : "OpenCode is disabled in T3 Code settings.",      },    });  }   return buildServerProvider({    provider: PROVIDER,    enabled: true,    checkedAt,    models,    probe: {      installed: false,      version: null,      status: "warning",      auth: { status: "unknown" },      message: "OpenCode provider status has not been checked in this session yet.",    },  });}; export function checkOpenCodeProviderStatus(input: {  readonly settings: OpenCodeSettings;  readonly cwd: string;}): Effect.Effect<ServerProvider> {  const checkedAt = new Date().toISOString();  const customModels = input.settings.customModels;  const isExternalServer = input.settings.serverUrl.trim().length > 0;   const fallback = (cause: unknown, version: string | null = null) => {    const failure = formatOpenCodeProbeError({      cause,      isExternalServer,      serverUrl: input.settings.serverUrl,    });    return buildServerProvider({      provider: PROVIDER,      enabled: input.settings.enabled,      checkedAt,      models: providerModelsFromSettings(        [],        PROVIDER,        customModels,        DEFAULT_OPENCODE_MODEL_CAPABILITIES,      ),      probe: {        installed: failure.installed,        version,        status: "error",        auth: { status: "unknown" },        message: failure.message,      },    });  };   return Effect.gen(function* () {    if (!input.settings.enabled) {      return buildServerProvider({        provider: PROVIDER,        enabled: false,        checkedAt,        models: providerModelsFromSettings(          [],          PROVIDER,          customModels,          DEFAULT_OPENCODE_MODEL_CAPABILITIES,        ),        probe: {          installed: false,          version: null,          status: "warning",          auth: { status: "unknown" },          message: isExternalServer            ? "OpenCode is disabled in T3 Code settings. A server URL is configured."            : "OpenCode is disabled in T3 Code settings.",        },      });    }     let version: string | null = null;    if (!isExternalServer) {      const versionExit = yield* Effect.exit(        Effect.tryPromise({          try: () =>            runOpenCodeCommand({              binaryPath: input.settings.binaryPath,              args: ["--version"],            }),          catch: toOpenCodeProbeError,        }),      );      if (versionExit._tag === "Failure") {        return fallback(Cause.squash(versionExit.cause));      }      version = parseGenericCliVersion(versionExit.value.stdout) ?? null;    }     const inventoryExit = yield* Effect.exit(      Effect.acquireUseRelease(        Effect.tryPromise({          try: () =>            connectToOpenCodeServer({              binaryPath: input.settings.binaryPath,              serverUrl: input.settings.serverUrl,            }),          catch: toOpenCodeProbeError,        }),        (server) =>          Effect.tryPromise({            try: async () => {              const client = createOpenCodeSdkClient({                baseUrl: server.url,                directory: input.cwd,                ...(isExternalServer && input.settings.serverPassword                  ? { serverPassword: input.settings.serverPassword }                  : {}),              });              return await loadOpenCodeInventory(client);            },            catch: toOpenCodeProbeError,          }),        (server) => Effect.sync(() => server.close()),      ),    );    if (inventoryExit._tag === "Failure") {      return fallback(Cause.squash(inventoryExit.cause), version);    }     const models = providerModelsFromSettings(      flattenOpenCodeModels(inventoryExit.value),      PROVIDER,      customModels,      DEFAULT_OPENCODE_MODEL_CAPABILITIES,    );    const connectedCount = inventoryExit.value.providerList.connected.length;    return buildServerProvider({      provider: PROVIDER,      enabled: true,      checkedAt,      models,      probe: {        installed: true,        version,        status: connectedCount > 0 ? "ready" : "warning",        auth: {          status: connectedCount > 0 ? "authenticated" : "unknown",          type: "opencode",        },        message:          connectedCount > 0            ? `${connectedCount} upstream provider${connectedCount === 1 ? "" : "s"} connected through ${isExternalServer ? "the configured OpenCode server" : "OpenCode"}.`            : isExternalServer              ? "Connected to the configured OpenCode server, but it did not report any connected upstream providers."              : "OpenCode is available, but it did not report any connected upstream providers.",      },    });  });} export function makeOpenCodeProviderLive() {  return Layer.effect(    OpenCodeProvider,    Effect.gen(function* () {      const serverSettings = yield* ServerSettingsService;      const serverConfig = yield* ServerConfig;       const getProviderSettings = serverSettings.getSettings.pipe(        Effect.map((settings) => settings.providers.opencode),      );       return yield* makeManagedServerProvider<OpenCodeSettings>({        getSettings: getProviderSettings.pipe(Effect.orDie),        streamSettings: serverSettings.streamChanges.pipe(          Stream.map((settings) => settings.providers.opencode),        ),        haveSettingsChanged: (previous, next) => !Equal.equals(previous, next),        initialSnapshot: makePendingOpenCodeProvider,        checkProvider: getProviderSettings.pipe(          Effect.flatMap((settings) =>            checkOpenCodeProviderStatus({              settings,              cwd: serverConfig.cwd,            }),          ),        ),      });    }),  );} export const OpenCodeProviderLive = makeOpenCodeProviderLive();