1168 lines · 37.5 KB · TypeScript
RawDownload
import * as nodeFs from "node:fs";import * as nodeOs from "node:os";import * as nodePath from "node:path"; import type {  CursorSettings,  ModelCapabilities,  ProviderOptionSelection,  ServerProvider,  ServerProviderAuth,  ServerProviderModel,  ServerProviderState,  ServerSettingsError,} from "@t3tools/contracts";import type * as EffectAcpSchema from "effect-acp/schema";import { Cause, Effect, Equal, Exit, Layer, Option, Result, Schema, Stream } from "effect";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";import {  createModelCapabilities,  getProviderOptionBooleanSelectionValue,  getProviderOptionStringSelectionValue,} from "@t3tools/shared/model"; import {  buildBooleanOptionDescriptor,  buildSelectOptionDescriptor,  buildServerProvider,  collectStreamAsString,  isCommandMissingCause,  providerModelsFromSettings,  type CommandResult,} from "../providerSnapshot.ts";import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";import { CursorProvider } from "../Services/CursorProvider.ts";import { AcpSessionRuntime } from "../acp/AcpSessionRuntime.ts";import { CursorListAvailableModelsResponse } from "../acp/CursorAcpExtension.ts";import { ServerSettingsService } from "../../serverSettings.ts"; const PROVIDER = "cursor" as const;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_REFRESH_INTERVAL = "1 hour";const CURSOR_PARAMETERIZED_MODEL_PICKER_MIN_VERSION_DATE = 2026_04_08;export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = {  _meta: {    parameterizedModelPicker: true,  },} satisfies NonNullable<EffectAcpSchema.InitializeRequest["clientCapabilities"]>; function buildInitialCursorProviderSnapshot(cursorSettings: CursorSettings): ServerProvider {  const checkedAt = new Date().toISOString();  const models = getCursorFallbackModels(cursorSettings);   if (!cursorSettings.enabled) {    return buildServerProvider({      provider: PROVIDER,      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({    provider: PROVIDER,    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 findCursorModelConfigOption(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption>,): EffectAcpSchema.SessionConfigOption | undefined {  return configOptions.find((option) => option.category === "model");} 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 hasCursorModelCapabilities(model: Pick<ServerProviderModel, "capabilities">): boolean {  return (model.capabilities?.optionDescriptors?.length ?? 0) > 0;} export function buildCursorDiscoveredModelsFromConfigOptions(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption> | null | undefined,): ReadonlyArray<ServerProviderModel> {  if (!configOptions || configOptions.length === 0) {    return [];  }   const modelOption = findCursorModelConfigOption(configOptions);  const modelChoices = flattenSessionConfigSelectOptions(modelOption);  if (!modelOption || modelChoices.length === 0) {    return [];  }   const currentModelValue =    modelOption.type === "select" ? modelOption.currentValue?.trim() || undefined : undefined;  const currentModelCapabilities = buildCursorCapabilitiesFromConfigOptions(configOptions);   return buildCursorDiscoveredModels(    modelChoices.map((modelChoice) => ({      slug: modelChoice.value.trim(),      name: modelChoice.name.trim(),      capabilities:        currentModelValue === modelChoice.value.trim()          ? currentModelCapabilities          : EMPTY_CAPABILITIES,    })),  );} 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) =>  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(),        },        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).pipe(Effect.provide(acpContext));  }); const withCursorAcpProbeRuntime = <A, E, R>(  cursorSettings: CursorSettings,  useRuntime: (acp: AcpSessionRuntime["Service"]) => Effect.Effect<A, E, R>,) => makeCursorAcpProbeRuntime(cursorSettings).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"),  );  if (reasoningOption && requestedReasoning) {    const value = findCursorSelectOptionValue(reasoningOption, (option) => {      const normalizedValue = normalizeCursorReasoningValue(option.value);      const normalizedName = normalizeCursorReasoningValue(option.name);      return normalizedValue === requestedReasoning || normalizedName === requestedReasoning;    });

Lines 1–500 of 1168.