import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Scope from "effect/Scope"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import type * as EffectAcpErrors from "effect-acp/errors"; import { CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES, resolveCursorAcpBaseModelId, resolveCursorAcpConfigUpdates, } from "../Layers/CursorProvider.ts"; import * as AcpSessionRuntime from "./AcpSessionRuntime.ts"; type CursorAcpRuntimeCursorSettings = Pick; export interface CursorAcpRuntimeInput extends Omit< AcpSessionRuntime.AcpSessionRuntimeOptions, "authMethodId" | "clientCapabilities" | "spawn" > { readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; readonly cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined; readonly environment?: NodeJS.ProcessEnv; } export interface CursorAcpModelSelectionErrorContext { readonly cause: EffectAcpErrors.AcpError; readonly step: "set-config-option" | "set-model"; readonly configId?: string; } export function buildCursorAcpSpawnInput( cursorSettings: CursorAcpRuntimeCursorSettings | null | undefined, cwd: string, environment?: NodeJS.ProcessEnv, ): AcpSessionRuntime.AcpSpawnInput { return { command: cursorSettings?.binaryPath || "cursor-agent", args: [ ...(cursorSettings?.apiEndpoint ? (["-e", cursorSettings.apiEndpoint] as const) : []), "acp", ], cwd, ...(environment ? { env: environment } : {}), }; } export const makeCursorAcpRuntime = ( input: CursorAcpRuntimeInput, ): Effect.Effect< AcpSessionRuntime.AcpSessionRuntime["Service"], EffectAcpErrors.AcpError, Crypto.Crypto | Scope.Scope > => Effect.gen(function* () { const acpContext = yield* Layer.build( AcpSessionRuntime.layer({ ...input, spawn: buildCursorAcpSpawnInput(input.cursorSettings, input.cwd, input.environment), authMethodId: "cursor_login", clientCapabilities: CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES, }).pipe( Layer.provide( Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, input.childProcessSpawner), ), ), ); return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe( Effect.provide(acpContext), ); }); interface CursorAcpModelSelectionRuntime { readonly getConfigOptions: AcpSessionRuntime.AcpSessionRuntime["Service"]["getConfigOptions"]; readonly setConfigOption: ( configId: string, value: string | boolean, ) => Effect.Effect; readonly setModel: (model: string) => Effect.Effect; } export function applyCursorAcpModelSelection(input: { readonly runtime: CursorAcpModelSelectionRuntime; readonly model: string | null | undefined; readonly selections: ReadonlyArray | null | undefined; readonly mapError: (context: CursorAcpModelSelectionErrorContext) => E; }): Effect.Effect { return Effect.gen(function* () { yield* input.runtime.setModel(resolveCursorAcpBaseModelId(input.model)).pipe( Effect.mapError((cause) => input.mapError({ cause, step: "set-model", }), ), ); const configUpdates = resolveCursorAcpConfigUpdates( yield* input.runtime.getConfigOptions, input.selections, ); for (const update of configUpdates) { yield* input.runtime.setConfigOption(update.configId, update.value).pipe( Effect.mapError((cause) => input.mapError({ cause, step: "set-config-option", configId: update.configId, }), ), ); } }); }