fix(cursor): support ACP parameterized model picker (#969)

* test: reproduce issue #968

* fix: support Cursor parameterized model picker (closes #968)
This commit is contained in:
SSU-WEI HUANG
2026-06-29 11:41:21 +08:00
committed by GitHub
parent 8493ec92f4
commit 5ade952218
8 changed files with 418 additions and 10 deletions
@@ -6,6 +6,18 @@ export type CursorModelsSnapshot = {
currentModelId: string | null;
};
type CursorAcpModelSnapshotBackend = Pick<AcpSdkBackend, 'getSessionModelsMetadata' | 'getConfigOptionByCategory'>
& Partial<Pick<AcpSdkBackend, 'getSessionConfigOptions'>>;
function findConfigOption(
backend: CursorAcpModelSnapshotBackend,
sessionId: string,
key: string
) {
return backend.getConfigOptionByCategory?.(sessionId, key)
?? backend.getSessionConfigOptions?.(sessionId)?.find((option) => option.id === key || option.category === key);
}
function mergeModelEntries(
target: Map<string, CursorModelSummary>,
entries: Iterable<{ modelId: string; name?: string | null }>
@@ -31,19 +43,43 @@ function mergeModelEntries(
* `availableModels` alone is often one variant per base family.
*/
export function buildCursorModelsSnapshotFromAcp(
backend: Pick<AcpSdkBackend, 'getSessionModelsMetadata' | 'getConfigOptionByCategory'>,
backend: CursorAcpModelSnapshotBackend,
sessionId: string
): CursorModelsSnapshot | null {
const metadata = backend.getSessionModelsMetadata(sessionId);
const modelOption = backend.getConfigOptionByCategory?.(sessionId, 'model');
const modelOption = findConfigOption(backend, sessionId, 'model');
const fastOption = findConfigOption(backend, sessionId, 'fast');
if (!metadata && !modelOption) {
return null;
}
const merged = new Map<string, CursorModelSummary>();
const parameterizedFastModels: CursorModelSummary[] = [];
if (modelOption?.options?.length) {
if (modelOption?.options?.length && fastOption?.options?.length) {
const fastValues = fastOption.options
.map((option) => option.value.trim())
.filter((value) => value === 'false' || value === 'true');
if (fastValues.length > 0) {
for (const option of modelOption.options) {
const modelId = option.value.trim();
if (!modelId || modelId.includes('[')) {
continue;
}
for (const fast of fastValues) {
parameterizedFastModels.push({
modelId: `${modelId}[fast=${fast}]`,
name: option.name
});
}
}
}
}
if (parameterizedFastModels.length > 0) {
mergeModelEntries(merged, parameterizedFastModels);
} else if (modelOption?.options?.length) {
mergeModelEntries(merged, modelOption.options.map((option) => ({
modelId: option.value,
name: option.name
@@ -51,16 +87,23 @@ export function buildCursorModelsSnapshotFromAcp(
}
if (metadata?.availableModels?.length) {
mergeModelEntries(merged, metadata.availableModels);
mergeModelEntries(
merged,
parameterizedFastModels.length > 0
? metadata.availableModels.filter((entry) => entry.modelId.includes('['))
: metadata.availableModels
);
}
if (merged.size === 0) {
return null;
}
const currentModelId = metadata?.currentModelId
?? modelOption?.currentValue
?? null;
const currentModelId = parameterizedFastModels.length > 0 && modelOption?.currentValue && fastOption?.currentValue
? `${modelOption.currentValue}[fast=${fastOption.currentValue}]`
: metadata?.currentModelId
?? modelOption?.currentValue
?? null;
return {
availableModels: [...merged.values()],