Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save juliusmarminge/65f5a313afcedaffe015716c7b7db13b to your computer and use it in GitHub Desktop.

Select an option

Save juliusmarminge/65f5a313afcedaffe015716c7b7db13b to your computer and use it in GitHub Desktop.
Cursor ACP In-session model switching reproduction
import { spawn } from "node:child_process";
import readline from "node:readline";
const bin = process.env.CURSOR_AGENT_BIN ?? "cursor-agent";
const cwd = process.argv[2] ?? process.cwd();
const targetModel = process.argv[3] ?? "gpt-5.5";
const timeoutMs = Number(process.env.CURSOR_ACP_TIMEOUT_MS ?? "20000");
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function summarizeSession(result) {
const configOptions = result?.configOptions ?? [];
return {
sessionId: result?.sessionId,
modelsCurrentModelId: result?.models?.currentModelId,
configCurrentValues: Object.fromEntries(
configOptions.map((option) => [option.id, option.currentValue]),
),
};
}
class AcpProcess {
constructor(args) {
this.child = spawn(bin, args, {
cwd,
env: process.env,
stdio: ["pipe", "pipe", "pipe"],
});
this.nextId = 1;
this.pending = new Map();
this.assistantText = "";
readline.createInterface({ input: this.child.stdout }).on("line", (line) => {
const message = JSON.parse(line);
if (message.method === "session/update") {
const update = message.params?.update;
if (update?.sessionUpdate === "agent_message_chunk" && update.content?.type === "text") {
this.assistantText += update.content.text;
}
return;
}
if (message.id === undefined || message.method) return;
const pending = this.pending.get(message.id);
if (!pending) return;
this.pending.delete(message.id);
clearTimeout(pending.timeout);
if (message.error) {
pending.reject(new Error(JSON.stringify(message.error)));
} else {
pending.resolve(message.result);
}
});
this.child.stderr.on("data", (chunk) => {
process.stderr.write(chunk);
});
}
request(method, params) {
const id = this.nextId++;
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`Timed out waiting for ${method}`));
}, timeoutMs);
this.pending.set(id, { resolve, reject, timeout });
});
}
async start() {
await this.request("initialize", {
protocolVersion: 1,
clientCapabilities: {
fs: { readTextFile: false, writeTextFile: false },
terminal: false,
_meta: { parameterizedModelPicker: true },
},
clientInfo: { name: "cursor-acp-model-switch-repro", version: "0.0.0" },
});
await this.request("authenticate", { methodId: "cursor_login" });
return this.request("session/new", { cwd, mcpServers: [] });
}
async promptForModel(sessionId) {
this.assistantText = "";
await this.request("session/prompt", {
sessionId,
prompt: [{ type: "text", text: "What model are you? Reply with only the exact model family and version." }],
});
return this.assistantText.trim();
}
async close() {
this.child.kill("SIGTERM");
await sleep(100);
if (!this.child.killed) this.child.kill("SIGKILL");
}
}
async function withAcp(args, run) {
const acp = new AcpProcess(args);
try {
return await run(acp);
} finally {
await acp.close();
}
}
console.log({ bin, cwd, targetModel });
await withAcp(["acp"], async (acp) => {
const created = await acp.start();
console.log("\n1. session/new without --model");
console.log(summarizeSession(created));
const setConfigResult = await acp.request("session/set_config_option", {
sessionId: created.sessionId,
configId: "model",
value: targetModel,
});
console.log("\n2. session/set_config_option model switch");
console.log(summarizeSession(setConfigResult));
console.log("assistant:", await acp.promptForModel(created.sessionId));
});
await withAcp(["acp"], async (acp) => {
const created = await acp.start();
console.log("\n3. fresh session/new before session/set_model");
console.log(summarizeSession(created));
const setModelResult = await acp.request("session/set_model", {
sessionId: created.sessionId,
modelId: targetModel,
});
console.log("\n4. session/set_model model switch");
console.log(setModelResult);
console.log("assistant:", await acp.promptForModel(created.sessionId));
});
await withAcp(["--model", targetModel, "acp"], async (acp) => {
const created = await acp.start();
console.log("\n5. session/new with startup --model");
console.log(summarizeSession(created));
console.log("assistant:", await acp.promptForModel(created.sessionId));
});
@juliusmarminge

Copy link
Copy Markdown
Author

Sample run whose output proves:

  • Initial ACP session: claude-opus-4-8
  • session/set_config_option requesting gpt-5.5: returns success, but model remains claude-opus-4-8
  • session/set_model requesting gpt-5.5: returns {}, but inference remains claude-opus-4-8
  • New process with cursor-agent --model gpt-5.5 acp: works and inference becomes GPT-5.5
julius@mac codething-mvp % node /tmp/cursor-acp-model-switch-repro.mjs
{
  bin: 'cursor-agent',
  cwd: '/Users/julius/.codex/worktrees/7351/codething-mvp',
  targetModel: 'gpt-5.5'
}

1. session/new without --model
{
  sessionId: 'da040ffa-c94a-4c4a-87fd-ae8f4efc473d',
  modelsCurrentModelId: 'claude-opus-4-8',
  configCurrentValues: {
    mode: 'agent',
    model: 'claude-opus-4-8',
    thinking: 'true',
    context: '300k',
    effort: 'xhigh',
    fast: 'false'
  }
}

2. session/set_config_option model switch
{
  sessionId: undefined,
  modelsCurrentModelId: undefined,
  configCurrentValues: {
    mode: 'agent',
    model: 'claude-opus-4-8',
    thinking: 'true',
    context: '300k',
    effort: 'xhigh',
    fast: 'false'
  }
}
assistant: Claude Opus 4.8

3. fresh session/new before session/set_model
{
  sessionId: '32e48daf-4b74-4b45-97a9-3bdaa8b6df1a',
  modelsCurrentModelId: 'claude-opus-4-8',
  configCurrentValues: {
    mode: 'agent',
    model: 'claude-opus-4-8',
    thinking: 'true',
    context: '300k',
    effort: 'xhigh',
    fast: 'false'
  }
}

4. session/set_model model switch
{}
assistant: Claude Opus 4.8

5. session/new with startup --model
{
  sessionId: 'c8ee8b76-12a9-4be8-8a33-f9dc7dbac693',
  modelsCurrentModelId: 'gpt-5.5',
  configCurrentValues: {
    mode: 'agent',
    model: 'gpt-5.5',
    context: '272k',
    reasoning: 'medium',
    fast: 'false'
  }
}
assistant: GPT-5.5
julius@mac codething-mvp %

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment