525 lines · 15.7 KB · TypeScript
RawDownload
import * as OS from "node:os";import type {  ModelCapabilities,  CodexModelOptions,  CodexSettings,  ServerProvider,  ServerProviderModel,  ServerProviderAuthStatus,  ServerProviderState,} from "@t3tools/contracts";import { Effect, Equal, FileSystem, Layer, Option, Path, Result, Stream } from "effect";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";import { resolveEffort } from "@t3tools/shared/model"; import {  buildServerProvider,  DEFAULT_TIMEOUT_MS,  detailFromResult,  extractAuthBoolean,  isCommandMissingCause,  parseGenericCliVersion,  providerModelsFromSettings,  spawnAndCollect,  type CommandResult,} from "../providerSnapshot";import { makeManagedServerProvider } from "../makeManagedServerProvider";import {  formatCodexCliUpgradeMessage,  isCodexCliVersionSupported,  parseCodexCliVersion,} from "../codexCliVersion";import { CodexProvider } from "../Services/CodexProvider";import { ServerSettingsError, ServerSettingsService } from "../../serverSettings"; const PROVIDER = "codex" as const;const OPENAI_AUTH_PROVIDERS = new Set(["openai"]);const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [  {    slug: "gpt-5.4",    name: "GPT-5.4",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },  {    slug: "gpt-5.4-mini",    name: "GPT-5.4 Mini",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },  {    slug: "gpt-5.3-codex",    name: "GPT-5.3 Codex",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },  {    slug: "gpt-5.3-codex-spark",    name: "GPT-5.3 Codex Spark",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },  {    slug: "gpt-5.2-codex",    name: "GPT-5.2 Codex",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },  {    slug: "gpt-5.2",    name: "GPT-5.2",    isCustom: false,    capabilities: {      reasoningEffortLevels: [        { value: "xhigh", label: "Extra High" },        { value: "high", label: "High", isDefault: true },        { value: "medium", label: "Medium" },        { value: "low", label: "Low" },      ],      supportsFastMode: true,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    },  },]; export function getCodexModelCapabilities(model: string | null | undefined): ModelCapabilities {  const slug = model?.trim();  return (    BUILT_IN_MODELS.find((candidate) => candidate.slug === slug)?.capabilities ?? {      reasoningEffortLevels: [],      supportsFastMode: false,      supportsThinkingToggle: false,      contextWindowOptions: [],      promptInjectedEffortLevels: [],    }  );} export function normalizeCodexModelOptions(  model: string | null | undefined,  modelOptions: CodexModelOptions | null | undefined,): CodexModelOptions | undefined {  const caps = getCodexModelCapabilities(model);  const reasoningEffort = resolveEffort(caps, modelOptions?.reasoningEffort);  const fastModeEnabled = modelOptions?.fastMode === true;  const nextOptions: CodexModelOptions = {    ...(reasoningEffort      ? { reasoningEffort: reasoningEffort as CodexModelOptions["reasoningEffort"] }      : {}),    ...(fastModeEnabled ? { fastMode: true } : {}),  };  return Object.keys(nextOptions).length > 0 ? nextOptions : undefined;} export function parseAuthStatusFromOutput(result: CommandResult): {  readonly status: Exclude<ServerProviderState, "disabled">;  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.",  };} export const readCodexConfigModelProvider = Effect.fn("readCodexConfigModelProvider")(function* () {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const settingsService = yield* ServerSettingsService;  const codexHome = yield* settingsService.getSettings.pipe(    Effect.map(      (settings) =>        settings.providers.codex.homePath ||        process.env.CODEX_HOME ||        path.join(OS.homedir(), ".codex"),    ),  );  const configPath = path.join(codexHome, "config.toml");   const content = yield* fileSystem    .readFileString(configPath)    .pipe(Effect.orElseSucceed(() => undefined));  if (content === undefined) {    return undefined;  }   let inTopLevel = true;  for (const line of content.split("\n")) {    const trimmed = line.trim();    if (!trimmed || trimmed.startsWith("#")) continue;    if (trimmed.startsWith("[")) {      inTopLevel = false;      continue;    }    if (!inTopLevel) continue;     const match = trimmed.match(/^model_provider\s*=\s*["']([^"']+)["']/);    if (match) return match[1];  }  return undefined;}); export const hasCustomModelProvider = readCodexConfigModelProvider().pipe(  Effect.map((provider) => provider !== undefined && !OPENAI_AUTH_PROVIDERS.has(provider)),  Effect.orElseSucceed(() => false),); const runCodexCommand = (args: ReadonlyArray<string>) =>  Effect.gen(function* () {    const settingsService = yield* ServerSettingsService;    const codexSettings = yield* settingsService.getSettings.pipe(      Effect.map((settings) => settings.providers.codex),    );    const command = ChildProcess.make(codexSettings.binaryPath, [...args], {      shell: process.platform === "win32",      env: {        ...process.env,        ...(codexSettings.homePath ? { CODEX_HOME: codexSettings.homePath } : {}),      },    });    return yield* spawnAndCollect(codexSettings.binaryPath, command);  }); export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(  function* (): Effect.fn.Return<    ServerProvider,    ServerSettingsError,    | ChildProcessSpawner.ChildProcessSpawner    | FileSystem.FileSystem    | Path.Path    | ServerSettingsService  > {    const codexSettings = yield* Effect.service(ServerSettingsService).pipe(      Effect.flatMap((service) => service.getSettings),      Effect.map((settings) => settings.providers.codex),    );    const checkedAt = new Date().toISOString();    const models = providerModelsFromSettings(      BUILT_IN_MODELS,      PROVIDER,      codexSettings.customModels,    );     if (!codexSettings.enabled) {      return buildServerProvider({        provider: PROVIDER,        enabled: false,        checkedAt,        models,        probe: {          installed: false,          version: null,          status: "warning",          authStatus: "unknown",          message: "Codex is disabled in T3 Code settings.",        },      });    }     const versionProbe = yield* runCodexCommand(["--version"]).pipe(      Effect.timeoutOption(DEFAULT_TIMEOUT_MS),      Effect.result,    );     if (Result.isFailure(versionProbe)) {      const error = versionProbe.failure;      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: !isCommandMissingCause(error),          version: null,          status: "error",          authStatus: "unknown",          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 buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: null,          status: "error",          authStatus: "unknown",          message: "Codex CLI is installed but failed to run. Timed out while running command.",        },      });    }     const version = versionProbe.success.value;    const parsedVersion =      parseCodexCliVersion(`${version.stdout}\n${version.stderr}`) ??      parseGenericCliVersion(`${version.stdout}\n${version.stderr}`);    if (version.code !== 0) {      const detail = detailFromResult(version);      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: parsedVersion,          status: "error",          authStatus: "unknown",          message: detail            ? `Codex CLI is installed but failed to run. ${detail}`            : "Codex CLI is installed but failed to run.",        },      });    }     if (parsedVersion && !isCodexCliVersionSupported(parsedVersion)) {      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: parsedVersion,          status: "error",          authStatus: "unknown",          message: formatCodexCliUpgradeMessage(parsedVersion),        },      });    }     if (yield* hasCustomModelProvider) {      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: parsedVersion,          status: "ready",          authStatus: "unknown",          message: "Using a custom Codex model provider; OpenAI login check skipped.",        },      });    }     const authProbe = yield* runCodexCommand(["login", "status"]).pipe(      Effect.timeoutOption(DEFAULT_TIMEOUT_MS),      Effect.result,    );     if (Result.isFailure(authProbe)) {      const error = authProbe.failure;      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: parsedVersion,          status: "warning",          authStatus: "unknown",          message:            error instanceof Error              ? `Could not verify Codex authentication status: ${error.message}.`              : "Could not verify Codex authentication status.",        },      });    }     if (Option.isNone(authProbe.success)) {      return buildServerProvider({        provider: PROVIDER,        enabled: codexSettings.enabled,        checkedAt,        models,        probe: {          installed: true,          version: parsedVersion,          status: "warning",          authStatus: "unknown",          message: "Could not verify Codex authentication status. Timed out while running command.",        },      });    }     const parsed = parseAuthStatusFromOutput(authProbe.success.value);    return buildServerProvider({      provider: PROVIDER,      enabled: codexSettings.enabled,      checkedAt,      models,      probe: {        installed: true,        version: parsedVersion,        status: parsed.status,        authStatus: parsed.authStatus,        ...(parsed.message ? { message: parsed.message } : {}),      },    });  },); export const CodexProviderLive = Layer.effect(  CodexProvider,  Effect.gen(function* () {

Lines 1–500 of 525.