Last active
August 28, 2026 17:14
-
-
Save avin/17b2ec44004896622d8ae45cc622cd4d to your computer and use it in GitHub Desktop.
Disable FAST mode for composer 2.5 in ACP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "agent_servers": { | |
| "MySimpleAgent": { | |
| "command": "C:\\Program Files\\Volta\\node.exe", | |
| "args": [ | |
| "C:\\Users\\avin\\.cursor\\cursor-acp-nonfast.js" | |
| ] | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use strict'; | |
| const fs = require('node:fs'); | |
| const path = require('node:path'); | |
| const { spawn } = require('node:child_process'); | |
| const readline = require('node:readline'); | |
| const home = process.env.USERPROFILE || process.env.HOME; | |
| if (!home) { | |
| throw new Error('USERPROFILE is not set'); | |
| } | |
| const cursorDir = path.join(home, '.cursor'); | |
| const cliConfigPath = path.join(cursorDir, 'cli-config.json'); | |
| const acpConfigPath = path.join(cursorDir, 'acp-config.json'); | |
| const agentScriptPath = path.join(home, 'AppData', 'Local', 'cursor-agent', 'agent.ps1'); | |
| // === Редактируемый список моделей ACP === | |
| // Для каждой модели указываются базовый modelId и параметры, которые | |
| // прослойка будет применять автоматически после выбора модели. | |
| const allowedModels = [ | |
| { | |
| modelId: 'composer-2.5', | |
| displayName: 'Composer 2.5 — Non-fast', | |
| parameters: [{ id: 'fast', value: 'false' }], | |
| }, | |
| { | |
| modelId: 'grok-4.6', | |
| displayName: 'Grok 4.6 — Medium — Non-fast', | |
| parameters: [ | |
| { id: 'effort', value: 'medium' }, | |
| { id: 'fast', value: 'false' }, | |
| ], | |
| }, | |
| ]; | |
| if (allowedModels.length === 0) { | |
| throw new Error('allowedModels must contain at least one model'); | |
| } | |
| const defaultModel = allowedModels[0]; | |
| const hiddenModelParameterIds = new Set([ | |
| 'fast', | |
| 'effort', | |
| 'reasoning', | |
| 'reasoning_effort', | |
| 'thinking', | |
| ]); | |
| function modelVariantId(model) { | |
| const parameters = model.parameters | |
| .map((parameter) => `${parameter.id}=${parameter.value}`) | |
| .join(','); | |
| return `${model.modelId}[${parameters}]`; | |
| } | |
| function cloneParameters(model = defaultModel) { | |
| return model.parameters.map((parameter) => ({ ...parameter })); | |
| } | |
| function resolveModel(value) { | |
| if (typeof value !== 'string') { | |
| return defaultModel; | |
| } | |
| return ( | |
| allowedModels.find( | |
| (model) => | |
| value === model.modelId || | |
| value === modelVariantId(model) || | |
| value.startsWith(`${model.modelId}[`), | |
| ) ?? defaultModel | |
| ); | |
| } | |
| function modelParameter(model, parameterId) { | |
| return model.parameters.find((parameter) => parameter.id === parameterId); | |
| } | |
| function readJson(filePath) { | |
| if (!fs.existsSync(filePath)) { | |
| return {}; | |
| } | |
| try { | |
| return JSON.parse(fs.readFileSync(filePath, 'utf8')); | |
| } catch (error) { | |
| console.error(`Could not read ${filePath}: ${error.message}`); | |
| return {}; | |
| } | |
| } | |
| function writeJson(filePath, value) { | |
| fs.mkdirSync(path.dirname(filePath), { recursive: true }); | |
| fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); | |
| } | |
| function forceLocalModelSelection() { | |
| const config = readJson(cliConfigPath); | |
| config.version = config.version || 1; | |
| config.hasChangedDefaultModel = true; | |
| config.model = { | |
| ...(config.model || {}), | |
| modelId: defaultModel.modelId, | |
| displayModelId: defaultModel.modelId, | |
| displayName: defaultModel.displayName, | |
| displayNameShort: defaultModel.displayName, | |
| aliases: Array.isArray(config.model?.aliases) ? config.model.aliases : [], | |
| maxMode: false, | |
| }; | |
| config.selectedModel = { | |
| modelId: defaultModel.modelId, | |
| parameters: cloneParameters(defaultModel), | |
| }; | |
| config.modelParameters = { | |
| ...(config.modelParameters || {}), | |
| }; | |
| for (const model of allowedModels) { | |
| config.modelParameters[model.modelId] = cloneParameters(model); | |
| } | |
| writeJson(cliConfigPath, config); | |
| const acpConfig = readJson(acpConfigPath); | |
| acpConfig.selectedModelVariantId = modelVariantId(defaultModel); | |
| writeJson(acpConfigPath, acpConfig); | |
| } | |
| function idKey(id) { | |
| return `${typeof id}:${String(id)}`; | |
| } | |
| function sendJson(stream, message) { | |
| stream.write(`${JSON.stringify(message)}\n`); | |
| } | |
| function isModelSelectionRequest(message) { | |
| return Boolean( | |
| message?.params && | |
| ((message.method === 'session/set_config_option' && | |
| message.params.configId === 'model') || | |
| message.method === 'session/set_model' || | |
| message.method === 'unstable_setSessionModel'), | |
| ); | |
| } | |
| function requestedModelValue(message) { | |
| if (message.method === 'session/set_config_option') { | |
| return message.params.value; | |
| } | |
| return message.params.modelId ?? message.params.model; | |
| } | |
| function findSourceModelEntry(entries, model) { | |
| return entries.find((entry) => { | |
| const value = entry?.modelId ?? entry?.value; | |
| return ( | |
| value === model.modelId || | |
| value === modelVariantId(model) || | |
| (typeof value === 'string' && value.startsWith(`${model.modelId}[`)) | |
| ); | |
| }); | |
| } | |
| function allowedModelOptions(sourceOptions) { | |
| return allowedModels.map((model) => { | |
| const source = findSourceModelEntry(sourceOptions, model); | |
| return { | |
| ...(source || {}), | |
| value: model.modelId, | |
| name: model.displayName, | |
| }; | |
| }); | |
| } | |
| function allowedAvailableModels(sourceModels) { | |
| return allowedModels.map((model) => { | |
| const source = findSourceModelEntry(sourceModels, model); | |
| return { | |
| ...(source || {}), | |
| modelId: model.modelId, | |
| name: model.displayName, | |
| }; | |
| }); | |
| } | |
| function patchClientRequest(message) { | |
| if (!message || typeof message !== 'object' || !message.method) { | |
| return message; | |
| } | |
| if (message.method === 'initialize' && message.params) { | |
| const params = { ...message.params }; | |
| const clientCapabilities = { ...(params.clientCapabilities || {}) }; | |
| clientCapabilities._meta = { | |
| ...(clientCapabilities._meta || {}), | |
| parameterizedModelPicker: true, | |
| }; | |
| params.clientCapabilities = clientCapabilities; | |
| return { ...message, params }; | |
| } | |
| if (message.method === 'session/set_config_option' && message.params) { | |
| const params = { ...message.params }; | |
| if (params.configId === 'model') { | |
| params.value = resolveModel(params.value).modelId; | |
| } else { | |
| const sessionModel = sessionModels.get(params.sessionId) ?? defaultModel; | |
| const configuredParameter = modelParameter(sessionModel, params.configId); | |
| if (configuredParameter) { | |
| params.value = configuredParameter.value; | |
| } | |
| } | |
| return { ...message, params }; | |
| } | |
| if ( | |
| (message.method === 'session/set_model' || | |
| message.method === 'unstable_setSessionModel') && | |
| message.params | |
| ) { | |
| const selectedModel = resolveModel(message.params.modelId ?? message.params.model); | |
| const params = { ...message.params, modelId: selectedModel.modelId }; | |
| if ('model' in params) { | |
| params.model = selectedModel.modelId; | |
| } | |
| return { ...message, params }; | |
| } | |
| return message; | |
| } | |
| function patchSessionResult(message, forcedModel = null) { | |
| if (!message?.result || typeof message.result !== 'object') { | |
| return message; | |
| } | |
| const result = { ...message.result }; | |
| const modelOption = Array.isArray(result.configOptions) | |
| ? result.configOptions.find((option) => option?.id === 'model') | |
| : null; | |
| const rawCurrentModel = | |
| result.models?.currentModelId ?? modelOption?.currentValue ?? defaultModel.modelId; | |
| const currentModel = forcedModel ?? resolveModel(rawCurrentModel); | |
| if (result.models && typeof result.models === 'object') { | |
| const models = { ...result.models }; | |
| if (Array.isArray(models.availableModels)) { | |
| models.availableModels = allowedAvailableModels(models.availableModels); | |
| } | |
| models.currentModelId = currentModel.modelId; | |
| result.models = models; | |
| } | |
| if (Array.isArray(result.configOptions)) { | |
| result.configOptions = result.configOptions.flatMap((option) => { | |
| if (option?.id === 'model') { | |
| const sourceOptions = Array.isArray(option.options) ? option.options : []; | |
| return [ | |
| { | |
| ...option, | |
| options: allowedModelOptions(sourceOptions), | |
| currentValue: currentModel.modelId, | |
| }, | |
| ]; | |
| } | |
| if (hiddenModelParameterIds.has(option?.id)) { | |
| return []; | |
| } | |
| return [option]; | |
| }); | |
| } | |
| return { ...message, result }; | |
| } | |
| forceLocalModelSelection(); | |
| const child = spawn( | |
| 'powershell.exe', | |
| [ | |
| '-NoProfile', | |
| '-ExecutionPolicy', | |
| 'Bypass', | |
| '-File', | |
| agentScriptPath, | |
| '--yolo', | |
| '--trust', | |
| 'acp', | |
| ], | |
| { | |
| stdio: ['pipe', 'pipe', 'inherit'], | |
| windowsHide: true, | |
| }, | |
| ); | |
| let internalRequestNumber = 0; | |
| const internalRequests = new Map(); | |
| const sessionSetupRequests = new Map(); | |
| const sessionModels = new Map(); | |
| function nextInternalRequestId() { | |
| return `cursor-model-menu-${++internalRequestNumber}`; | |
| } | |
| function emitModelSelectionResponse(selection, successful) { | |
| if (successful && selection.sessionId) { | |
| sessionModels.set(selection.sessionId, selection.model); | |
| } | |
| if (!selection.heldResponse) { | |
| return; | |
| } | |
| const response = { ...selection.heldResponse }; | |
| if (!selection.isSetup) { | |
| if (selection.clientRequestId === undefined || selection.clientRequestId === null) { | |
| return; | |
| } | |
| response.id = selection.clientRequestId; | |
| } | |
| process.stdout.write( | |
| `${JSON.stringify( | |
| patchSessionResult(response, successful || selection.isSetup ? selection.model : null), | |
| )}\n`, | |
| ); | |
| } | |
| function emitModelSelectionFailure(selection, message) { | |
| if (selection.isSetup) { | |
| emitModelSelectionResponse(selection, false); | |
| return; | |
| } | |
| if (selection.clientRequestId === undefined || selection.clientRequestId === null) { | |
| return; | |
| } | |
| process.stdout.write(`${JSON.stringify({ ...message, id: selection.clientRequestId })}\n`); | |
| } | |
| function sendNextModelParameter(selection) { | |
| if (selection.parameterIndex >= selection.model.parameters.length) { | |
| emitModelSelectionResponse(selection, true); | |
| return; | |
| } | |
| const parameter = selection.model.parameters[selection.parameterIndex++]; | |
| const requestId = nextInternalRequestId(); | |
| internalRequests.set(idKey(requestId), { | |
| stage: 'parameter', | |
| selection, | |
| }); | |
| sendJson(child.stdin, { | |
| jsonrpc: '2.0', | |
| id: requestId, | |
| method: 'session/set_config_option', | |
| params: { | |
| sessionId: selection.sessionId, | |
| configId: parameter.id, | |
| value: parameter.value, | |
| }, | |
| }); | |
| } | |
| function beginModelSelection({ sessionId, model, heldResponse = null, clientRequest = null }) { | |
| const isSetup = clientRequest === null; | |
| const selection = { | |
| sessionId, | |
| model, | |
| heldResponse, | |
| isSetup, | |
| clientRequestId: clientRequest?.id, | |
| parameterIndex: 0, | |
| }; | |
| const requestId = nextInternalRequestId(); | |
| internalRequests.set(idKey(requestId), { | |
| stage: 'model', | |
| selection, | |
| }); | |
| if (clientRequest) { | |
| const params = { ...clientRequest.params }; | |
| if (clientRequest.method === 'session/set_config_option') { | |
| params.value = model.modelId; | |
| } else { | |
| params.modelId = model.modelId; | |
| if ('model' in params) { | |
| params.model = model.modelId; | |
| } | |
| } | |
| sendJson(child.stdin, { ...clientRequest, id: requestId, params }); | |
| return; | |
| } | |
| sendJson(child.stdin, { | |
| jsonrpc: '2.0', | |
| id: requestId, | |
| method: 'session/set_config_option', | |
| params: { sessionId, configId: 'model', value: model.modelId }, | |
| }); | |
| } | |
| function handleInternalResponse(message) { | |
| if (!message || message.id === undefined || message.id === null) { | |
| return false; | |
| } | |
| const key = idKey(message.id); | |
| const request = internalRequests.get(key); | |
| if (!request) { | |
| return false; | |
| } | |
| internalRequests.delete(key); | |
| if (request.stage === 'model') { | |
| if (message.error) { | |
| emitModelSelectionFailure(request.selection, message); | |
| } else { | |
| if (!request.selection.heldResponse) { | |
| request.selection.heldResponse = message; | |
| } | |
| sendNextModelParameter(request.selection); | |
| } | |
| } else if (message.error) { | |
| emitModelSelectionFailure(request.selection, message); | |
| } else { | |
| sendNextModelParameter(request.selection); | |
| } | |
| return true; | |
| } | |
| const childOutput = readline.createInterface({ | |
| input: child.stdout, | |
| crlfDelay: Infinity, | |
| }); | |
| childOutput.on('line', (line) => { | |
| let message; | |
| try { | |
| message = JSON.parse(line); | |
| } catch { | |
| process.stdout.write(`${line}\n`); | |
| return; | |
| } | |
| // ACP permission requests are normally forwarded to WebStorm, which then | |
| // shows a confirmation dialog. This bridge is intentionally unattended: | |
| // approve the strongest option offered by Cursor and keep the request away | |
| // from the IDE client. | |
| if ( | |
| message?.method === 'session/request_permission' && | |
| message.id !== undefined && | |
| message.id !== null | |
| ) { | |
| const options = Array.isArray(message.params?.options) | |
| ? message.params.options | |
| : []; | |
| const selectedOption = | |
| options.find((option) => option?.optionId === 'allow-always') ?? | |
| options.find((option) => option?.optionId === 'allow-once'); | |
| sendJson(child.stdin, { | |
| jsonrpc: '2.0', | |
| id: message.id, | |
| result: { | |
| outcome: { | |
| outcome: 'selected', | |
| optionId: selectedOption?.optionId ?? 'allow-once', | |
| }, | |
| }, | |
| }); | |
| return; | |
| } | |
| if (handleInternalResponse(message)) { | |
| return; | |
| } | |
| if (message?.id !== undefined && message?.id !== null) { | |
| const setupKey = idKey(message.id); | |
| const setup = sessionSetupRequests.get(setupKey); | |
| if (setup) { | |
| sessionSetupRequests.delete(setupKey); | |
| if (!message.error && message.result?.sessionId) { | |
| beginModelSelection({ | |
| sessionId: message.result.sessionId, | |
| model: defaultModel, | |
| heldResponse: message, | |
| }); | |
| return; | |
| } | |
| } | |
| } | |
| process.stdout.write(`${JSON.stringify(patchSessionResult(message))}\n`); | |
| }); | |
| const clientInput = readline.createInterface({ | |
| input: process.stdin, | |
| crlfDelay: Infinity, | |
| }); | |
| clientInput.on('line', (line) => { | |
| let message; | |
| try { | |
| message = JSON.parse(line); | |
| } catch { | |
| child.stdin.write(`${line}\n`); | |
| return; | |
| } | |
| if ( | |
| message?.id !== undefined && | |
| message?.id !== null && | |
| ['session/new', 'session/load', 'session/resume'].includes(message.method) | |
| ) { | |
| sessionSetupRequests.set(idKey(message.id), true); | |
| } | |
| if (isModelSelectionRequest(message) && message.params.sessionId) { | |
| beginModelSelection({ | |
| sessionId: message.params.sessionId, | |
| model: resolveModel(requestedModelValue(message)), | |
| clientRequest: message, | |
| }); | |
| return; | |
| } | |
| sendJson(child.stdin, patchClientRequest(message)); | |
| }); | |
| clientInput.on('close', () => { | |
| child.stdin.end(); | |
| }); | |
| child.on('error', (error) => { | |
| console.error(`Cursor ACP process failed: ${error.message}`); | |
| }); | |
| child.on('exit', (code, signal) => { | |
| if (signal) { | |
| process.exitCode = 1; | |
| } else { | |
| process.exitCode = code ?? 1; | |
| } | |
| }); | |
| function stop() { | |
| if (!child.killed) { | |
| child.kill(); | |
| } | |
| } | |
| process.on('SIGINT', stop); | |
| process.on('SIGTERM', stop); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment