179 lines · 6.8 KB · TypeScript
RawDownload
/** * CursorDriver — `ProviderDriver` for the Cursor Agent (`agent`) runtime. * * Cursor exposes an ACP-based CLI. The driver is still a plain value, but * its snapshot uses `makeManagedServerProvider`'s optional `enrichSnapshot` * hook to run the slow ACP model-capability probe in the background without * blocking the initial `ready`-state publish. * * Text generation is supported via the ACP runtime — `makeCursorTextGeneration` * drives `runtime.prompt` with a structured-output schema and collects the * agent's `agent_message_chunk` stream into a single JSON blob. * * @module provider/Drivers/CursorDriver */import { CursorSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import { HttpClient } from "effect/unstable/http";import { ChildProcessSpawner } from "effect/unstable/process"; import { ServerConfig } from "../../config.ts";import { makeCursorTextGeneration } from "../../textGeneration/CursorTextGeneration.ts";import { ProviderDriverError } from "../Errors.ts";import { makeCursorAdapter } from "../Layers/CursorAdapter.ts";import {  buildInitialCursorProviderSnapshot,  checkCursorProviderStatus,  enrichCursorSnapshot,} from "../Layers/CursorProvider.ts";import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";import {  defaultProviderContinuationIdentity,  type ProviderDriver,  type ProviderInstance,} from "../ProviderDriver.ts";import type { ServerProviderDraft } from "../providerSnapshot.ts";import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";import {  makeProviderMaintenanceCapabilities,  makeStaticProviderMaintenanceResolver,  resolveProviderMaintenanceCapabilitiesEffect,} from "../providerMaintenance.ts";const decodeCursorSettings = Schema.decodeSync(CursorSettings); const DRIVER_KIND = ProviderDriverKind.make("cursor");const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5);const UPDATE = makeStaticProviderMaintenanceResolver(  makeProviderMaintenanceCapabilities({    provider: DRIVER_KIND,    packageName: null,    updateExecutable: "agent",    updateArgs: ["update"],    updateLockKey: "cursor-agent",  }),); export type CursorDriverEnv =  | ChildProcessSpawner.ChildProcessSpawner  | FileSystem.FileSystem  | HttpClient.HttpClient  | Path.Path  | ProviderEventLoggers  | ServerConfig; const withInstanceIdentity =  (input: {    readonly instanceId: ProviderInstance["instanceId"];    readonly displayName: string | undefined;    readonly accentColor: string | undefined;    readonly continuationGroupKey: string;  }) =>  (snapshot: ServerProviderDraft): ServerProvider => ({    ...snapshot,    instanceId: input.instanceId,    driver: DRIVER_KIND,    ...(input.displayName ? { displayName: input.displayName } : {}),    ...(input.accentColor ? { accentColor: input.accentColor } : {}),    continuation: { groupKey: input.continuationGroupKey },  }); export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = {  driverKind: DRIVER_KIND,  metadata: {    displayName: "Cursor",    supportsMultipleInstances: true,  },  configSchema: CursorSettings,  defaultConfig: (): CursorSettings => decodeCursorSettings({}),  create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>    Effect.gen(function* () {      const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const httpClient = yield* HttpClient.HttpClient;      const eventLoggers = yield* ProviderEventLoggers;      const processEnv = mergeProviderInstanceEnvironment(environment);      const continuationIdentity = defaultProviderContinuationIdentity({        driverKind: DRIVER_KIND,        instanceId,      });      const stampIdentity = withInstanceIdentity({        instanceId,        displayName,        accentColor,        continuationGroupKey: continuationIdentity.continuationKey,      });      const effectiveConfig = { ...config, enabled } satisfies CursorSettings;      const maintenanceCapabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {        binaryPath: effectiveConfig.binaryPath,        env: processEnv,      });       const adapter = yield* makeCursorAdapter(effectiveConfig, {        environment: processEnv,        ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),        instanceId,      });      const textGeneration = yield* makeCursorTextGeneration(effectiveConfig, processEnv);       const checkProvider = checkCursorProviderStatus(effectiveConfig, processEnv).pipe(        Effect.map(stampIdentity),        Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),        Effect.provideService(FileSystem.FileSystem, fileSystem),        Effect.provideService(Path.Path, path),      );       const snapshot = yield* makeManagedServerProvider<CursorSettings>({        maintenanceCapabilities,        getSettings: Effect.succeed(effectiveConfig),        streamSettings: Stream.never,        haveSettingsChanged: () => false,        initialSnapshot: (settings) =>          buildInitialCursorProviderSnapshot(settings).pipe(Effect.map(stampIdentity)),        checkProvider,        // Preserve the background ACP model-capability probe that used to        // live on `CursorProviderLive`. Only fires when the snapshot reports        // an authenticated, enabled provider with at least one non-custom        // model whose capabilities haven't been captured yet.        enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) =>          enrichCursorSnapshot({            settings,            environment: processEnv,            snapshot: currentSnapshot,            maintenanceCapabilities,            publishSnapshot,            stampIdentity,            httpClient,          }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner)),        refreshInterval: SNAPSHOT_REFRESH_INTERVAL,      }).pipe(        Effect.mapError(          (cause) =>            new ProviderDriverError({              driver: DRIVER_KIND,              instanceId,              detail: `Failed to build Cursor snapshot: ${cause.message ?? String(cause)}`,              cause,            }),        ),      );       return {        instanceId,        driverKind: DRIVER_KIND,        continuationIdentity,        displayName,        accentColor,        enabled,        snapshot,        adapter,        textGeneration,      } satisfies ProviderInstance;    }),};