1176 lines · 37.3 KB · TypeScript
RawDownload
import * as NodeOS from "node:os";import type {  CursorSettings,  ModelCapabilities,  ProviderOptionSelection,  ServerProvider,  ServerProviderAuth,  ServerProviderModel,  ServerProviderState,} from "@t3tools/contracts";import type * as EffectAcpSchema from "effect-acp/schema";import { causeErrorTag } from "@t3tools/shared/observability";import * as Cache from "effect/Cache";import * as Duration from "effect/Duration";import * as Crypto from "effect/Crypto";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import { HttpClient } from "effect/unstable/http";import * as ChildProcess from "effect/unstable/process/ChildProcess";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import {  createModelCapabilities,  getProviderOptionBooleanSelectionValue,  getProviderOptionStringSelectionValue,} from "@t3tools/shared/model";import { resolveSpawnCommand } from "@t3tools/shared/shell"; import {  buildBooleanOptionDescriptor,  buildSelectOptionDescriptor,  buildServerProvider,  COMPACT_SLASH_COMMAND,  collectStreamAsString,  isCommandMissingCause,  providerModelsFromSettings,  type CommandResult,  type ServerProviderDraft,} from "../providerSnapshot.ts";import {  enrichProviderSnapshotWithVersionAdvisory,  type ProviderMaintenanceCapabilities,} from "../providerMaintenance.ts";import * as AcpSessionRuntime from "../acp/AcpSessionRuntime.ts";import { CursorListAvailableModelsResponse } from "../acp/CursorAcpExtension.ts"; const decodeCursorListAvailableModelsResponse = Schema.decodeUnknownEffect(  CursorListAvailableModelsResponse,);const CURSOR_PRESENTATION = {  displayName: "Cursor",  badgeLabel: "Early Access",  showInteractionModeToggle: true,} as const;const EMPTY_CAPABILITIES: ModelCapabilities = createModelCapabilities({  optionDescriptors: [],}); const CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS = 15_000;const CURSOR_PARAMETERIZED_MODEL_PICKER_MIN_VERSION_DATE = 2026_04_08;const CURSOR_CLI_INSTALLATION_DOCS_URL = "https://cursor.com/docs/cli/installation";const CURSOR_ACP_MODEL_DISCOVERY_FAILED_MESSAGE = [  "Cursor ACP model discovery failed.",  "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.",  `See ${CURSOR_CLI_INSTALLATION_DOCS_URL}.`,  "Check server logs for ACP details.",].join(" ");export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = {  _meta: {    parameterizedModelPicker: true,  },} satisfies NonNullable<EffectAcpSchema.InitializeRequest["clientCapabilities"]>; export function buildInitialCursorProviderSnapshot(  cursorSettings: CursorSettings,): Effect.Effect<ServerProviderDraft> {  return Effect.gen(function* () {    const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso);    const models = getCursorFallbackModels(cursorSettings);     if (!cursorSettings.enabled) {      return buildServerProvider({        presentation: CURSOR_PRESENTATION,        enabled: false,        checkedAt,        models,        probe: {          installed: false,          version: null,          status: "warning",          auth: { status: "unknown" },          message: "Cursor is disabled in T3 Code settings.",        },      });    }     return buildServerProvider({      presentation: CURSOR_PRESENTATION,      enabled: true,      checkedAt,      models,      probe: {        installed: true,        version: null,        status: "warning",        auth: { status: "unknown" },        message: "Checking Cursor Agent availability...",      },    });  });} interface CursorSessionSelectOption {  readonly value: string;  readonly name: string;} interface CursorAcpDiscoveredModel {  readonly slug: string;  readonly name: string;  readonly capabilities: ModelCapabilities;} function flattenSessionConfigSelectOptions(  configOption: EffectAcpSchema.SessionConfigOption | undefined,): ReadonlyArray<CursorSessionSelectOption> {  if (!configOption || configOption.type !== "select") {    return [];  }  return configOption.options.flatMap((entry) =>    "value" in entry      ? [          {            value: entry.value.trim(),            name: entry.name.trim(),          } satisfies CursorSessionSelectOption,        ]      : entry.options.map(          (option) =>            ({              value: option.value.trim(),              name: option.name.trim(),            }) satisfies CursorSessionSelectOption,        ),  );} function normalizeCursorReasoningValue(value: string | null | undefined): string | undefined {  const normalized = value?.trim().toLowerCase();  switch (normalized) {    case "low":    case "medium":    case "high":    case "max":      return normalized;    case "xhigh":    case "extra-high":    case "extra high":      return "xhigh";    default:      return undefined;  }} function getCursorConfigOptionCategory(option: EffectAcpSchema.SessionConfigOption): string {  return option.category?.trim().toLowerCase() ?? "";} function isCursorEffortConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean {  const id = option.id.trim().toLowerCase();  const name = option.name.trim().toLowerCase();  return (    id === "effort" ||    id === "reasoning" ||    name === "effort" ||    name === "reasoning" ||    name.includes("effort") ||    name.includes("reasoning")  );} function findCursorEffortConfigOption(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption>,): EffectAcpSchema.SessionConfigOption | undefined {  const candidates = configOptions.filter(    (option) => option.type === "select" && isCursorEffortConfigOption(option),  );  return (    candidates.find((option) => getCursorConfigOptionCategory(option) === "model_option") ??    candidates.find((option) => option.id.trim().toLowerCase() === "effort") ??    candidates.find((option) => getCursorConfigOptionCategory(option) === "thought_level") ??    candidates[0]  );} function isCursorContextConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean {  const id = option.id.trim().toLowerCase();  const name = option.name.trim().toLowerCase();  return id === "context" || id === "context_size" || name.includes("context");} function isCursorFastConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean {  const id = option.id.trim().toLowerCase();  const name = option.name.trim().toLowerCase();  return id === "fast" || name === "fast" || name.includes("fast mode");} function isCursorThinkingConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean {  const id = option.id.trim().toLowerCase();  const name = option.name.trim().toLowerCase();  return id === "thinking" || name.includes("thinking");} function isBooleanLikeConfigOption(option: EffectAcpSchema.SessionConfigOption): boolean {  if (option.type === "boolean") {    return true;  }  if (option.type !== "select") {    return false;  }  const values = new Set(    flattenSessionConfigSelectOptions(option).map((entry) => entry.value.trim().toLowerCase()),  );  return values.has("true") && values.has("false");} function getBooleanCurrentValue(  option: EffectAcpSchema.SessionConfigOption | undefined,): boolean | undefined {  if (!option) {    return undefined;  }  if (option.type === "boolean") {    return option.currentValue;  }  if (option.type !== "select") {    return undefined;  }  const normalized = option.currentValue?.trim().toLowerCase();  if (normalized === "true") {    return true;  }  if (normalized === "false") {    return false;  }  return undefined;} export function buildCursorCapabilitiesFromConfigOptions(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption> | null | undefined,): ModelCapabilities {  if (!configOptions || configOptions.length === 0) {    return EMPTY_CAPABILITIES;  }   const reasoningConfig = findCursorEffortConfigOption(configOptions);  const reasoningEffortLevels =    reasoningConfig?.type === "select"      ? flattenSessionConfigSelectOptions(reasoningConfig).flatMap((entry) => {          const normalizedValue = normalizeCursorReasoningValue(entry.value);          if (!normalizedValue) {            return [];          }          return [            {              value: normalizedValue,              label: entry.name,              ...(normalizeCursorReasoningValue(reasoningConfig.currentValue) === normalizedValue                ? { isDefault: true }                : {}),            },          ];        })      : [];   const contextOption = configOptions.find(    (option) => option.category === "model_config" && isCursorContextConfigOption(option),  );  const contextWindowOptions =    contextOption?.type === "select"      ? flattenSessionConfigSelectOptions(contextOption).map((entry) => {          if (contextOption.currentValue === entry.value) {            return {              value: entry.value,              label: entry.name,              isDefault: true,            };          }          return {            value: entry.value,            label: entry.name,          };        })      : [];   const fastOption = configOptions.find(    (option) => option.category === "model_config" && isCursorFastConfigOption(option),  );  const thinkingOption = configOptions.find(    (option) => option.category === "model_config" && isCursorThinkingConfigOption(option),  );  const fastCurrentValue = getBooleanCurrentValue(fastOption);  const thinkingCurrentValue = getBooleanCurrentValue(thinkingOption);  const optionDescriptors = [    ...(reasoningEffortLevels.length > 0      ? [          buildSelectOptionDescriptor({            id: "reasoning",            label: reasoningConfig?.name?.trim() || "Reasoning",            options: reasoningEffortLevels,          }),        ]      : []),    ...(contextWindowOptions.length > 0      ? [          buildSelectOptionDescriptor({            id: "contextWindow",            label: contextOption?.name?.trim() || "Context Window",            options: contextWindowOptions,          }),        ]      : []),    ...(fastOption && isBooleanLikeConfigOption(fastOption)      ? [          typeof fastCurrentValue === "boolean"            ? buildBooleanOptionDescriptor({                id: "fastMode",                label: fastOption.name?.trim() || "Fast Mode",                currentValue: fastCurrentValue,              })            : buildBooleanOptionDescriptor({                id: "fastMode",                label: fastOption.name?.trim() || "Fast Mode",              }),        ]      : []),    ...(thinkingOption && isBooleanLikeConfigOption(thinkingOption)      ? [          typeof thinkingCurrentValue === "boolean"            ? buildBooleanOptionDescriptor({                id: "thinking",                label: thinkingOption.name?.trim() || "Thinking",                currentValue: thinkingCurrentValue,              })            : buildBooleanOptionDescriptor({                id: "thinking",                label: thinkingOption.name?.trim() || "Thinking",              }),        ]      : []),  ];   return createModelCapabilities({    optionDescriptors,  });} function buildCursorDiscoveredModels(  discoveredModels: ReadonlyArray<CursorAcpDiscoveredModel>,): ReadonlyArray<ServerProviderModel> {  const seen = new Set<string>();  return discoveredModels.flatMap((model) => {    if (!model.slug || seen.has(model.slug)) {      return [];    }    seen.add(model.slug);    return [      {        slug: model.slug,        name: model.name,        isCustom: false,        capabilities: model.capabilities,      } satisfies ServerProviderModel,    ];  });} function buildCursorDiscoveredModelsFromAvailableModelsResponse(  response: typeof CursorListAvailableModelsResponse.Type,): ReadonlyArray<ServerProviderModel> {  return buildCursorDiscoveredModels(    response.models.flatMap((model) => {      const slug = model.value.trim();      const name = model.name.trim();      if (!slug || !name) {        return [];      }       return [        {          slug,          name,          capabilities: buildCursorCapabilitiesFromConfigOptions(model.configOptions),        },      ];    }),  );} const makeCursorAcpProbeRuntime = (  cursorSettings: CursorSettings,  environment?: NodeJS.ProcessEnv,) =>  Effect.gen(function* () {    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;    const acpContext = yield* Layer.build(      AcpSessionRuntime.layer({        spawn: {          command: cursorSettings.binaryPath,          args: [            ...(cursorSettings.apiEndpoint ? (["-e", cursorSettings.apiEndpoint] as const) : []),            "acp",          ],          cwd: process.cwd(),          ...(environment ? { env: environment } : {}),        },        cwd: process.cwd(),        clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" },        authMethodId: "cursor_login",        clientCapabilities: CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES,      }).pipe(Layer.provide(Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner))),    );    return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe(      Effect.provide(acpContext),    );  }); const withCursorAcpProbeRuntime = <A, E, R>(  cursorSettings: CursorSettings,  useRuntime: (acp: AcpSessionRuntime.AcpSessionRuntime["Service"]) => Effect.Effect<A, E, R>,  environment?: NodeJS.ProcessEnv,) =>  makeCursorAcpProbeRuntime(cursorSettings, environment).pipe(    Effect.flatMap(useRuntime),    Effect.scoped,  ); function normalizeCursorConfigOptionToken(value: string | null | undefined): string {  return (    value      ?.trim()      .toLowerCase()      .replace(/[\s_-]+/g, "-") ?? ""  );} function findCursorSelectOptionValue(  configOption: EffectAcpSchema.SessionConfigOption | undefined,  matcher: (option: CursorSessionSelectOption) => boolean,): string | undefined {  return flattenSessionConfigSelectOptions(configOption).find(matcher)?.value;} function findCursorBooleanConfigValue(  configOption: EffectAcpSchema.SessionConfigOption | undefined,  requested: boolean,): string | boolean | undefined {  if (!configOption) {    return undefined;  }  if (configOption.type === "boolean") {    return requested;  }  return findCursorSelectOptionValue(    configOption,    (option) => normalizeCursorConfigOptionToken(option.value) === String(requested),  );} export function resolveCursorAcpBaseModelId(model: string | null | undefined): string {  const trimmed = model?.trim();  const base = trimmed && trimmed.length > 0 ? trimmed : "default";  return base.includes("[") ? base.slice(0, base.indexOf("[")) : base;} export function resolveCursorAcpConfigUpdates(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption> | null | undefined,  selections: ReadonlyArray<ProviderOptionSelection> | null | undefined,): ReadonlyArray<{  readonly configId: string;  readonly value: string | boolean;}> {  if (!configOptions || configOptions.length === 0) {    return [];  }   const updates: Array<{    readonly configId: string;    readonly value: string | boolean;  }> = [];   const reasoningOption = findCursorEffortConfigOption(configOptions);  const requestedReasoning = normalizeCursorReasoningValue(    getProviderOptionStringSelectionValue(selections, "reasoning"),

Lines 1–500 of 1176.