Choose non-fast variants for ACP models (#1952)
* fix(providers): expose ACP fast-mode selection Provider wrappers now forward draft feature lookups to inner ACP clients, and ACP adapters can add initialize metadata for model parameterization. Fast-mode config options flow through the existing provider feature path. * test(providers): cover wrapped ACP command lookup * fix(providers): submit Cursor non-fast defaults * fix(app): preserve parameterized model preferences * fix(providers): preserve Cursor ACP feature params * fix(providers): keep Cursor legacy model fallbacks * fix(providers): preserve Cursor model parameter switches * refactor(providers): simplify ACP model feature negotiation
This commit is contained in:
@@ -3,8 +3,10 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
|
|||||||
import { createTestLogger } from "../../test-utils/test-logger.js";
|
import { createTestLogger } from "../../test-utils/test-logger.js";
|
||||||
import type {
|
import type {
|
||||||
AgentClient,
|
AgentClient,
|
||||||
|
AgentFeature,
|
||||||
AgentModelDefinition,
|
AgentModelDefinition,
|
||||||
AgentMode,
|
AgentMode,
|
||||||
|
AgentSessionConfig,
|
||||||
ProviderCatalog,
|
ProviderCatalog,
|
||||||
} from "./agent-sdk-types.js";
|
} from "./agent-sdk-types.js";
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ const mockState = vi.hoisted(() => {
|
|||||||
},
|
},
|
||||||
isCommandAvailable: vi.fn(async (_command: string) => false),
|
isCommandAvailable: vi.fn(async (_command: string) => false),
|
||||||
runtimeModels: new Map<string, AgentModelDefinition[]>(),
|
runtimeModels: new Map<string, AgentModelDefinition[]>(),
|
||||||
|
cursorListFeaturesConfigs: [] as AgentSessionConfig[],
|
||||||
reset() {
|
reset() {
|
||||||
this.constructorArgs.claude = [];
|
this.constructorArgs.claude = [];
|
||||||
this.constructorArgs.codex = [];
|
this.constructorArgs.codex = [];
|
||||||
@@ -52,6 +55,7 @@ const mockState = vi.hoisted(() => {
|
|||||||
this.isCommandAvailable.mockReset();
|
this.isCommandAvailable.mockReset();
|
||||||
this.isCommandAvailable.mockImplementation(async (_command: string) => false);
|
this.isCommandAvailable.mockImplementation(async (_command: string) => false);
|
||||||
this.runtimeModels.clear();
|
this.runtimeModels.clear();
|
||||||
|
this.cursorListFeaturesConfigs = [];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -378,6 +382,19 @@ vi.mock("./providers/cursor-acp-agent.js", () => ({
|
|||||||
async isAvailable(): Promise<boolean> {
|
async isAvailable(): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listFeatures(config: AgentSessionConfig): Promise<AgentFeature[]> {
|
||||||
|
mockState.cursorListFeaturesConfigs.push(config);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
id: "fast",
|
||||||
|
label: "Fast",
|
||||||
|
value: "false",
|
||||||
|
options: [{ id: "false", label: "Off" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -697,6 +714,41 @@ test("cursor provider extending acp uses CursorACPAgentClient", () => {
|
|||||||
expect(mockState.constructorArgs.genericAcp).toEqual([]);
|
expect(mockState.constructorArgs.genericAcp).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("wrapped cursor client lists ACP features through the inner provider", async () => {
|
||||||
|
const registry = buildProviderRegistry(logger, {
|
||||||
|
providerOverrides: {
|
||||||
|
cursor: {
|
||||||
|
extends: "acp",
|
||||||
|
label: "Cursor",
|
||||||
|
command: ["cursor-agent", "acp"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = registry.cursor.createClient(logger);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.listFeatures?.({
|
||||||
|
provider: "cursor",
|
||||||
|
cwd: "/tmp/cursor",
|
||||||
|
}),
|
||||||
|
).resolves.toEqual([
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
id: "fast",
|
||||||
|
label: "Fast",
|
||||||
|
value: "false",
|
||||||
|
options: [{ id: "false", label: "Off" }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(mockState.cursorListFeaturesConfigs).toEqual([
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
cwd: "/tmp/cursor",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("traecli provider extending acp uses TraeACPAgentClient", () => {
|
test("traecli provider extending acp uses TraeACPAgentClient", () => {
|
||||||
const registry = buildProviderRegistry(logger, {
|
const registry = buildProviderRegistry(logger, {
|
||||||
providerOverrides: {
|
providerOverrides: {
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ function wrapClientProvider(
|
|||||||
): AgentClient {
|
): AgentClient {
|
||||||
const listImportableSessions = inner.listImportableSessions?.bind(inner);
|
const listImportableSessions = inner.listImportableSessions?.bind(inner);
|
||||||
const importSession = inner.importSession?.bind(inner);
|
const importSession = inner.importSession?.bind(inner);
|
||||||
|
const listFeatures = inner.listFeatures?.bind(inner);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider,
|
provider,
|
||||||
@@ -441,6 +442,9 @@ function wrapClientProvider(
|
|||||||
},
|
},
|
||||||
resolveCreateConfig: inner.resolveCreateConfig?.bind(inner),
|
resolveCreateConfig: inner.resolveCreateConfig?.bind(inner),
|
||||||
isCreateConfigUnattended: inner.isCreateConfigUnattended?.bind(inner),
|
isCreateConfigUnattended: inner.isCreateConfigUnattended?.bind(inner),
|
||||||
|
listFeatures: listFeatures
|
||||||
|
? async (config) => await listFeatures({ ...config, provider: inner.provider })
|
||||||
|
: undefined,
|
||||||
listImportableSessions: listImportableSessions
|
listImportableSessions: listImportableSessions
|
||||||
? async (options) => await listImportableSessions(options)
|
? async (options) => await listImportableSessions(options)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export const DEFAULT_ACP_CAPABILITIES: AgentCapabilityFlags = {
|
|||||||
supportsRewindBoth: false,
|
supportsRewindBoth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACP_CLIENT_CAPABILITIES: ACPClientCapabilities = {
|
const BASE_ACP_CLIENT_CAPABILITIES: ACPClientCapabilities = {
|
||||||
fs: {
|
fs: {
|
||||||
readTextFile: true,
|
readTextFile: true,
|
||||||
writeTextFile: true,
|
writeTextFile: true,
|
||||||
@@ -237,6 +237,18 @@ const ACP_CLIENT_CAPABILITIES: ACPClientCapabilities = {
|
|||||||
terminal: true,
|
terminal: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ACPClientCapabilityMeta = Record<string, unknown>;
|
||||||
|
|
||||||
|
export function buildACPClientCapabilities(meta?: ACPClientCapabilityMeta): ACPClientCapabilities {
|
||||||
|
if (!meta || Object.keys(meta).length === 0) {
|
||||||
|
return BASE_ACP_CLIENT_CAPABILITIES;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...BASE_ACP_CLIENT_CAPABILITIES,
|
||||||
|
_meta: meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Suppress interactive auth side-effects (e.g. Gemini CLI opening a Google
|
// Suppress interactive auth side-effects (e.g. Gemini CLI opening a Google
|
||||||
// sign-in URL in the browser) when probing an ACP agent for models/modes.
|
// sign-in URL in the browser) when probing an ACP agent for models/modes.
|
||||||
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
|
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
|
||||||
@@ -359,6 +371,7 @@ interface ACPAgentClientOptions {
|
|||||||
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
|
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
|
||||||
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
|
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
|
||||||
configFeatureOptions?: ACPConfigFeatureOption[];
|
configFeatureOptions?: ACPConfigFeatureOption[];
|
||||||
|
clientCapabilityMeta?: ACPClientCapabilityMeta;
|
||||||
modeIdTransformer?: (modeId: string) => string | null;
|
modeIdTransformer?: (modeId: string) => string | null;
|
||||||
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
||||||
providerModeWriter?: (
|
providerModeWriter?: (
|
||||||
@@ -387,6 +400,7 @@ interface ACPAgentSessionOptions {
|
|||||||
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
|
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
|
||||||
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
|
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
|
||||||
configFeatureOptions?: ACPConfigFeatureOption[];
|
configFeatureOptions?: ACPConfigFeatureOption[];
|
||||||
|
clientCapabilityMeta?: ACPClientCapabilityMeta;
|
||||||
modeIdTransformer?: (modeId: string) => string | null;
|
modeIdTransformer?: (modeId: string) => string | null;
|
||||||
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
||||||
providerModeWriter?: (
|
providerModeWriter?: (
|
||||||
@@ -486,7 +500,7 @@ interface ConfigOptionSelector {
|
|||||||
export interface ACPConfigFeatureOption {
|
export interface ACPConfigFeatureOption {
|
||||||
id: string;
|
id: string;
|
||||||
configId: string;
|
configId: string;
|
||||||
category: string;
|
category?: string;
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
@@ -694,6 +708,7 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
configOptions: SessionConfigOption[],
|
configOptions: SessionConfigOption[],
|
||||||
) => SessionConfigOption[];
|
) => SessionConfigOption[];
|
||||||
private readonly configFeatureOptions: ACPConfigFeatureOption[];
|
private readonly configFeatureOptions: ACPConfigFeatureOption[];
|
||||||
|
private readonly clientCapabilityMeta?: ACPClientCapabilityMeta;
|
||||||
private readonly modeIdTransformer?: (modeId: string) => string | null;
|
private readonly modeIdTransformer?: (modeId: string) => string | null;
|
||||||
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
||||||
private readonly providerModeWriter?: (
|
private readonly providerModeWriter?: (
|
||||||
@@ -727,6 +742,7 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
||||||
this.configOptionsTransformer = options.configOptionsTransformer;
|
this.configOptionsTransformer = options.configOptionsTransformer;
|
||||||
this.configFeatureOptions = options.configFeatureOptions ?? [];
|
this.configFeatureOptions = options.configFeatureOptions ?? [];
|
||||||
|
this.clientCapabilityMeta = options.clientCapabilityMeta;
|
||||||
this.modeIdTransformer = options.modeIdTransformer;
|
this.modeIdTransformer = options.modeIdTransformer;
|
||||||
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
||||||
this.providerModeWriter = options.providerModeWriter;
|
this.providerModeWriter = options.providerModeWriter;
|
||||||
@@ -754,6 +770,7 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
sessionResponseTransformer: this.sessionResponseTransformer,
|
sessionResponseTransformer: this.sessionResponseTransformer,
|
||||||
configOptionsTransformer: this.configOptionsTransformer,
|
configOptionsTransformer: this.configOptionsTransformer,
|
||||||
configFeatureOptions: this.configFeatureOptions,
|
configFeatureOptions: this.configFeatureOptions,
|
||||||
|
clientCapabilityMeta: this.clientCapabilityMeta,
|
||||||
modeIdTransformer: this.modeIdTransformer,
|
modeIdTransformer: this.modeIdTransformer,
|
||||||
toolSnapshotTransformer: this.toolSnapshotTransformer,
|
toolSnapshotTransformer: this.toolSnapshotTransformer,
|
||||||
providerModeWriter: this.providerModeWriter,
|
providerModeWriter: this.providerModeWriter,
|
||||||
@@ -802,6 +819,7 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
sessionResponseTransformer: this.sessionResponseTransformer,
|
sessionResponseTransformer: this.sessionResponseTransformer,
|
||||||
configOptionsTransformer: this.configOptionsTransformer,
|
configOptionsTransformer: this.configOptionsTransformer,
|
||||||
configFeatureOptions: this.configFeatureOptions,
|
configFeatureOptions: this.configFeatureOptions,
|
||||||
|
clientCapabilityMeta: this.clientCapabilityMeta,
|
||||||
modeIdTransformer: this.modeIdTransformer,
|
modeIdTransformer: this.modeIdTransformer,
|
||||||
toolSnapshotTransformer: this.toolSnapshotTransformer,
|
toolSnapshotTransformer: this.toolSnapshotTransformer,
|
||||||
providerModeWriter: this.providerModeWriter,
|
providerModeWriter: this.providerModeWriter,
|
||||||
@@ -1039,7 +1057,7 @@ export class ACPAgentClient implements AgentClient {
|
|||||||
Promise.race([
|
Promise.race([
|
||||||
transport.connection.initialize({
|
transport.connection.initialize({
|
||||||
protocolVersion: PROTOCOL_VERSION,
|
protocolVersion: PROTOCOL_VERSION,
|
||||||
clientCapabilities: ACP_CLIENT_CAPABILITIES,
|
clientCapabilities: buildACPClientCapabilities(this.clientCapabilityMeta),
|
||||||
clientInfo: { name: "Paseo", version: "dev" },
|
clientInfo: { name: "Paseo", version: "dev" },
|
||||||
}),
|
}),
|
||||||
transport.spawnError,
|
transport.spawnError,
|
||||||
@@ -1251,6 +1269,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
configOptions: SessionConfigOption[],
|
configOptions: SessionConfigOption[],
|
||||||
) => SessionConfigOption[];
|
) => SessionConfigOption[];
|
||||||
private readonly configFeatureOptions: ACPConfigFeatureOption[];
|
private readonly configFeatureOptions: ACPConfigFeatureOption[];
|
||||||
|
private readonly clientCapabilityMeta?: ACPClientCapabilityMeta;
|
||||||
private readonly modeIdTransformer?: (modeId: string) => string | null;
|
private readonly modeIdTransformer?: (modeId: string) => string | null;
|
||||||
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
|
||||||
private readonly providerModeWriter?: (
|
private readonly providerModeWriter?: (
|
||||||
@@ -1315,6 +1334,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
this.sessionResponseTransformer = options.sessionResponseTransformer;
|
||||||
this.configOptionsTransformer = options.configOptionsTransformer;
|
this.configOptionsTransformer = options.configOptionsTransformer;
|
||||||
this.configFeatureOptions = options.configFeatureOptions ?? [];
|
this.configFeatureOptions = options.configFeatureOptions ?? [];
|
||||||
|
this.clientCapabilityMeta = options.clientCapabilityMeta;
|
||||||
this.modeIdTransformer = options.modeIdTransformer;
|
this.modeIdTransformer = options.modeIdTransformer;
|
||||||
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
|
||||||
this.providerModeWriter = options.providerModeWriter;
|
this.providerModeWriter = options.providerModeWriter;
|
||||||
@@ -1890,16 +1910,19 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
}: {
|
}: {
|
||||||
response: { configOptions: SessionConfigOption[] };
|
response: { configOptions: SessionConfigOption[] };
|
||||||
configId: string;
|
configId: string;
|
||||||
category: string;
|
category?: string;
|
||||||
requestedValue: string;
|
requestedValue: string;
|
||||||
label: string;
|
label: string;
|
||||||
}): string {
|
}): string {
|
||||||
this.configOptions = this.transformConfigOptions(response.configOptions);
|
this.configOptions = this.transformConfigOptions(response.configOptions);
|
||||||
const responseOption = findSelectConfigOption({
|
const responseOption =
|
||||||
configOptions: this.configOptions,
|
category === undefined
|
||||||
category,
|
? findSelectConfigOptionById({ configOptions: this.configOptions, id: configId })
|
||||||
id: configId,
|
: findSelectConfigOption({
|
||||||
});
|
configOptions: this.configOptions,
|
||||||
|
category,
|
||||||
|
id: configId,
|
||||||
|
});
|
||||||
if (responseOption?.currentValue != null) {
|
if (responseOption?.currentValue != null) {
|
||||||
return responseOption.currentValue;
|
return responseOption.currentValue;
|
||||||
}
|
}
|
||||||
@@ -2297,7 +2320,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
const initialize = await this.runACPRequest(() =>
|
const initialize = await this.runACPRequest(() =>
|
||||||
connection.initialize({
|
connection.initialize({
|
||||||
protocolVersion: PROTOCOL_VERSION,
|
protocolVersion: PROTOCOL_VERSION,
|
||||||
clientCapabilities: ACP_CLIENT_CAPABILITIES,
|
clientCapabilities: buildACPClientCapabilities(this.clientCapabilityMeta),
|
||||||
clientInfo: { name: "Paseo", version: "dev" },
|
clientInfo: { name: "Paseo", version: "dev" },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -2743,6 +2766,19 @@ function findSelectConfigOption({
|
|||||||
return option ?? null;
|
return option ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSelectConfigOptionById({
|
||||||
|
configOptions,
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
configOptions: SessionConfigOption[] | null | undefined;
|
||||||
|
id: string;
|
||||||
|
}): SelectConfigOption | null {
|
||||||
|
const option = configOptions?.find(
|
||||||
|
(entry): entry is SelectConfigOption => entry.type === "select" && entry.id === id,
|
||||||
|
);
|
||||||
|
return option ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
function findSelectConfigFeatureOption(
|
function findSelectConfigFeatureOption(
|
||||||
configOptions: SessionConfigOption[] | null | undefined,
|
configOptions: SessionConfigOption[] | null | undefined,
|
||||||
featureOption: ACPConfigFeatureOption,
|
featureOption: ACPConfigFeatureOption,
|
||||||
@@ -2751,7 +2787,7 @@ function findSelectConfigFeatureOption(
|
|||||||
(entry): entry is SelectConfigOption =>
|
(entry): entry is SelectConfigOption =>
|
||||||
entry.type === "select" &&
|
entry.type === "select" &&
|
||||||
entry.id === featureOption.configId &&
|
entry.id === featureOption.configId &&
|
||||||
entry.category === featureOption.category,
|
(featureOption.category === undefined || entry.category === featureOption.category),
|
||||||
);
|
);
|
||||||
return option ?? null;
|
return option ?? null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
import { describe, expect, test, vi } from "vitest";
|
import { describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
import type { SpawnedACPProcess, SessionStateResponse } from "./acp-agent.js";
|
import type { SpawnedACPProcess, SessionStateResponse } from "./acp-agent.js";
|
||||||
import { CursorACPAgentClient } from "./cursor-acp-agent.js";
|
import { CURSOR_FAST_FEATURE_OPTION, CursorACPAgentClient } from "./cursor-acp-agent.js";
|
||||||
import { createTestLogger } from "../../../test-utils/test-logger.js";
|
import { createTestLogger } from "../../../test-utils/test-logger.js";
|
||||||
|
|
||||||
describe("CursorACPAgentClient model discovery", () => {
|
describe("CursorACPAgentClient model discovery", () => {
|
||||||
|
function fastConfigOption(currentValue: "false" | "true") {
|
||||||
|
return {
|
||||||
|
id: "fast",
|
||||||
|
name: "Fast",
|
||||||
|
type: "select" as const,
|
||||||
|
currentValue,
|
||||||
|
options: [
|
||||||
|
{ value: "false", name: "Off" },
|
||||||
|
{ value: "true", name: "Fast" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
class TestCursorACPAgentClient extends CursorACPAgentClient {
|
class TestCursorACPAgentClient extends CursorACPAgentClient {
|
||||||
constructor(response: SessionStateResponse) {
|
constructor(response: SessionStateResponse) {
|
||||||
super({
|
super({
|
||||||
@@ -77,4 +89,79 @@ describe("CursorACPAgentClient model discovery", () => {
|
|||||||
modes: [],
|
modes: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps modern Cursor models as plain ACP ids", async () => {
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
sessionId: "session-1",
|
||||||
|
models: {
|
||||||
|
currentModelId: "composer-2.5",
|
||||||
|
availableModels: [
|
||||||
|
{
|
||||||
|
modelId: "composer-2.5",
|
||||||
|
name: "Composer 2.5",
|
||||||
|
description: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
configOptions: [fastConfigOption("false")],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.fetchCatalog({ scope: "workspace", cwd: "/tmp/cursor", force: false }),
|
||||||
|
).resolves.toEqual({
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "composer-2.5",
|
||||||
|
label: "Composer 2.5",
|
||||||
|
description: undefined,
|
||||||
|
isDefault: true,
|
||||||
|
thinkingOptions: undefined,
|
||||||
|
defaultThinkingOptionId: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
modes: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exposes Cursor fast mode through provider features", async () => {
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
sessionId: "session-1",
|
||||||
|
models: null,
|
||||||
|
configOptions: [fastConfigOption("false")],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.listFeatures({
|
||||||
|
provider: "acp",
|
||||||
|
cwd: "/tmp/cursor",
|
||||||
|
}),
|
||||||
|
).resolves.toEqual([
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
id: CURSOR_FAST_FEATURE_OPTION.id,
|
||||||
|
label: "Fast",
|
||||||
|
description: "Cursor fast mode",
|
||||||
|
tooltip: "Select Cursor fast mode",
|
||||||
|
icon: "zap",
|
||||||
|
value: "false",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "false",
|
||||||
|
label: "Off",
|
||||||
|
isDefault: true,
|
||||||
|
description: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "true",
|
||||||
|
label: "Fast",
|
||||||
|
isDefault: false,
|
||||||
|
description: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Logger } from "pino";
|
import type { Logger } from "pino";
|
||||||
|
|
||||||
|
import type { ACPConfigFeatureOption } from "./acp-agent.js";
|
||||||
import { GenericACPAgentClient } from "./generic-acp-agent.js";
|
import { GenericACPAgentClient } from "./generic-acp-agent.js";
|
||||||
|
|
||||||
interface CursorACPAgentClientOptions {
|
interface CursorACPAgentClientOptions {
|
||||||
@@ -12,6 +13,18 @@ interface CursorACPAgentClientOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CURSOR_INITIAL_COMMANDS_WAIT_TIMEOUT_MS = 10_000;
|
const CURSOR_INITIAL_COMMANDS_WAIT_TIMEOUT_MS = 10_000;
|
||||||
|
const CURSOR_CLIENT_CAPABILITY_META = {
|
||||||
|
parameterizedModelPicker: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CURSOR_FAST_FEATURE_OPTION: ACPConfigFeatureOption = {
|
||||||
|
id: "fast",
|
||||||
|
configId: "fast",
|
||||||
|
label: "Fast",
|
||||||
|
description: "Cursor fast mode",
|
||||||
|
tooltip: "Select Cursor fast mode",
|
||||||
|
icon: "zap",
|
||||||
|
};
|
||||||
|
|
||||||
export class CursorACPAgentClient extends GenericACPAgentClient {
|
export class CursorACPAgentClient extends GenericACPAgentClient {
|
||||||
constructor(options: CursorACPAgentClientOptions) {
|
constructor(options: CursorACPAgentClientOptions) {
|
||||||
@@ -25,6 +38,8 @@ export class CursorACPAgentClient extends GenericACPAgentClient {
|
|||||||
// cursor-agent publishes slash commands asynchronously via available_commands_update.
|
// cursor-agent publishes slash commands asynchronously via available_commands_update.
|
||||||
waitForInitialCommands: true,
|
waitForInitialCommands: true,
|
||||||
initialCommandsWaitTimeoutMs: CURSOR_INITIAL_COMMANDS_WAIT_TIMEOUT_MS,
|
initialCommandsWaitTimeoutMs: CURSOR_INITIAL_COMMANDS_WAIT_TIMEOUT_MS,
|
||||||
|
clientCapabilityMeta: CURSOR_CLIENT_CAPABILITY_META,
|
||||||
|
configFeatureOptions: [CURSOR_FAST_FEATURE_OPTION],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type { AgentCapabilityFlags } from "../agent-sdk-types.js";
|
|||||||
import { checkProviderLaunchAvailable, resolveProviderLaunch } from "../provider-launch-config.js";
|
import { checkProviderLaunchAvailable, resolveProviderLaunch } from "../provider-launch-config.js";
|
||||||
import {
|
import {
|
||||||
ACPAgentClient,
|
ACPAgentClient,
|
||||||
|
type ACPClientCapabilityMeta,
|
||||||
|
type ACPConfigFeatureOption,
|
||||||
DEFAULT_ACP_CAPABILITIES,
|
DEFAULT_ACP_CAPABILITIES,
|
||||||
type ACPExtensionCommandsParser,
|
type ACPExtensionCommandsParser,
|
||||||
} from "./acp-agent.js";
|
} from "./acp-agent.js";
|
||||||
@@ -33,6 +35,8 @@ interface GenericACPAgentClientOptions {
|
|||||||
waitForInitialCommands?: boolean;
|
waitForInitialCommands?: boolean;
|
||||||
initialCommandsWaitTimeoutMs?: number;
|
initialCommandsWaitTimeoutMs?: number;
|
||||||
diagnosticPhaseTimeoutMs?: number;
|
diagnosticPhaseTimeoutMs?: number;
|
||||||
|
clientCapabilityMeta?: ACPClientCapabilityMeta;
|
||||||
|
configFeatureOptions?: ACPConfigFeatureOption[];
|
||||||
extensionCommandsParser?: ACPExtensionCommandsParser;
|
extensionCommandsParser?: ACPExtensionCommandsParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +57,8 @@ export class GenericACPAgentClient extends ACPAgentClient {
|
|||||||
capabilities: buildGenericACPCapabilities(options),
|
capabilities: buildGenericACPCapabilities(options),
|
||||||
waitForInitialCommands: options.waitForInitialCommands,
|
waitForInitialCommands: options.waitForInitialCommands,
|
||||||
initialCommandsWaitTimeoutMs: options.initialCommandsWaitTimeoutMs,
|
initialCommandsWaitTimeoutMs: options.initialCommandsWaitTimeoutMs,
|
||||||
|
clientCapabilityMeta: options.clientCapabilityMeta,
|
||||||
|
configFeatureOptions: options.configFeatureOptions,
|
||||||
extensionCommandsParser: options.extensionCommandsParser,
|
extensionCommandsParser: options.extensionCommandsParser,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user