diff --git a/src/local-agent-acp.test.ts b/src/local-agent-acp.test.ts index 1aa2c077e..f20f02020 100644 --- a/src/local-agent-acp.test.ts +++ b/src/local-agent-acp.test.ts @@ -14,6 +14,7 @@ import { deliverAcpSessionUpdate, resolveAcpCommand, selectAcpPermissionOption, + matchAcpSelectValue, } from "./local-agent-acp.js"; import { GrokPromptCompletionRegistry } from "./local-agent-grok.js"; @@ -822,3 +823,12 @@ test("a stop requested before the prompt is sent starts no provider work", async assert.equal(preAbortRequests.includes("session/prompt"), false); await preAbortRuntime.close(); }); + +test("an ACP select accepts a plain name when exactly one bracketed variant matches", () => { + const available = ["default[]", "composer-2.5[fast=true]", "grok-4.6[effort=high,fast=true]", "grok-4.6[effort=low]"]; + + assert.equal(matchAcpSelectValue(available, "composer-2.5[fast=true]"), "composer-2.5[fast=true]"); + assert.equal(matchAcpSelectValue(available, "composer-2.5"), "composer-2.5[fast=true]"); + assert.equal(matchAcpSelectValue(available, "grok-4.6"), undefined, "ambiguous names match nothing"); + assert.equal(matchAcpSelectValue(available, "composer"), undefined); +}); diff --git a/src/local-agent-acp.ts b/src/local-agent-acp.ts index 784624a34..ec09ab03b 100644 --- a/src/local-agent-acp.ts +++ b/src/local-agent-acp.ts @@ -954,13 +954,26 @@ function resolveAcpSelectConfigUpdate( if (!configId) throw new Error(`${options.provider} ACP ${options.label} is missing an id.`); const available = flattenAcpSelectValues(config); + const value = matchAcpSelectValue(available, options.value); - if (!available.includes(options.value)) { + if (!value) { const suffix = available.length > 0 ? ` Available values: ${available.join(", ")}.` : ""; throw new Error(`${options.provider} ACP ${options.label} does not support '${options.value}'.${suffix}`); } - return { sessionId, configId, value: options.value }; + return { sessionId, configId, value }; +} + +/** + * Accepts an exact option value, or a plain name such as `composer-2.5` when exactly one option is that name plus + * bracketed settings (Cursor's `composer-2.5[fast=true]`). An ambiguous name matches nothing. + */ +export function matchAcpSelectValue(available: readonly string[], requested: string): string | undefined { + if (available.includes(requested)) return requested; + + const variants = available.filter((value) => value.startsWith(`${requested}[`) && value.endsWith("]")); + + return variants.length === 1 ? variants[0] : undefined; } export function flattenAcpSelectValues(option: JsonObject): string[] {