@@ -13,6 +13,10 @@ const mockState = vi.hoisted(() => {
|
|||||||
claude: [] as ConstructorEntry[],
|
claude: [] as ConstructorEntry[],
|
||||||
codex: [] as ConstructorEntry[],
|
codex: [] as ConstructorEntry[],
|
||||||
copilot: [] as ConstructorEntry[],
|
copilot: [] as ConstructorEntry[],
|
||||||
|
cursor: [] as Array<{
|
||||||
|
command: string[];
|
||||||
|
env?: Record<string, string>;
|
||||||
|
}>,
|
||||||
pi: [] as ConstructorEntry[],
|
pi: [] as ConstructorEntry[],
|
||||||
genericAcp: [] as Array<{
|
genericAcp: [] as Array<{
|
||||||
command: string[];
|
command: string[];
|
||||||
@@ -27,6 +31,7 @@ const mockState = vi.hoisted(() => {
|
|||||||
this.constructorArgs.claude = [];
|
this.constructorArgs.claude = [];
|
||||||
this.constructorArgs.codex = [];
|
this.constructorArgs.codex = [];
|
||||||
this.constructorArgs.copilot = [];
|
this.constructorArgs.copilot = [];
|
||||||
|
this.constructorArgs.cursor = [];
|
||||||
this.constructorArgs.pi = [];
|
this.constructorArgs.pi = [];
|
||||||
this.constructorArgs.genericAcp = [];
|
this.constructorArgs.genericAcp = [];
|
||||||
this.isCommandAvailable.mockReset();
|
this.isCommandAvailable.mockReset();
|
||||||
@@ -286,6 +291,55 @@ vi.mock("./providers/generic-acp-agent.js", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("./providers/cursor-acp-agent.js", () => ({
|
||||||
|
CursorACPAgentClient: class CursorACPAgentClient {
|
||||||
|
readonly capabilities = {
|
||||||
|
supportsStreaming: true,
|
||||||
|
supportsSessionPersistence: true,
|
||||||
|
supportsDynamicModes: true,
|
||||||
|
supportsMcpServers: true,
|
||||||
|
supportsReasoningStream: true,
|
||||||
|
supportsToolInvocations: true,
|
||||||
|
};
|
||||||
|
readonly provider = "acp";
|
||||||
|
readonly runtimeSettings?: unknown;
|
||||||
|
|
||||||
|
constructor(options: { command: string[]; env?: Record<string, string> }) {
|
||||||
|
this.runtimeSettings = {
|
||||||
|
command: {
|
||||||
|
mode: "replace",
|
||||||
|
argv: options.command,
|
||||||
|
},
|
||||||
|
env: options.env,
|
||||||
|
};
|
||||||
|
mockState.constructorArgs.cursor.push({
|
||||||
|
command: options.command,
|
||||||
|
env: options.env,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(): Promise<never> {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async resumeSession(): Promise<never> {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async listModels(): Promise<AgentModelDefinition[]> {
|
||||||
|
return mockState.runtimeModels.get(this.provider) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async listModes(): Promise<[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async isAvailable(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AGENT_PROVIDER_DEFINITIONS,
|
AGENT_PROVIDER_DEFINITIONS,
|
||||||
buildProviderRegistry,
|
buildProviderRegistry,
|
||||||
@@ -410,6 +464,38 @@ test("new provider extending acp uses GenericACPAgentClient", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("cursor provider extending acp uses CursorACPAgentClient", () => {
|
||||||
|
const registry = buildProviderRegistry(logger, {
|
||||||
|
providerOverrides: {
|
||||||
|
cursor: {
|
||||||
|
extends: "acp",
|
||||||
|
label: "Cursor",
|
||||||
|
command: ["cursor-agent", "acp"],
|
||||||
|
env: {
|
||||||
|
CURSOR_AGENT_LOG: "debug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(registry.cursor.createClient(logger).provider).toBe("cursor");
|
||||||
|
expect(mockState.constructorArgs.cursor).toEqual([
|
||||||
|
{
|
||||||
|
command: ["cursor-agent", "acp"],
|
||||||
|
env: {
|
||||||
|
CURSOR_AGENT_LOG: "debug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: ["cursor-agent", "acp"],
|
||||||
|
env: {
|
||||||
|
CURSOR_AGENT_LOG: "debug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(mockState.constructorArgs.genericAcp).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
test('extends: "acp" without command throws', () => {
|
test('extends: "acp" without command throws', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
buildProviderRegistry(logger, {
|
buildProviderRegistry(logger, {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
import { ClaudeAgentClient } from "./providers/claude/agent.js";
|
import { ClaudeAgentClient } from "./providers/claude/agent.js";
|
||||||
import { CodexAppServerAgentClient } from "./providers/codex-app-server-agent.js";
|
import { CodexAppServerAgentClient } from "./providers/codex-app-server-agent.js";
|
||||||
import { CopilotACPAgentClient } from "./providers/copilot-acp-agent.js";
|
import { CopilotACPAgentClient } from "./providers/copilot-acp-agent.js";
|
||||||
|
import { CursorACPAgentClient } from "./providers/cursor-acp-agent.js";
|
||||||
import { GenericACPAgentClient } from "./providers/generic-acp-agent.js";
|
import { GenericACPAgentClient } from "./providers/generic-acp-agent.js";
|
||||||
import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
|
import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
|
||||||
import { OpenCodeServerManager } from "./providers/opencode/server-manager.js";
|
import { OpenCodeServerManager } from "./providers/opencode/server-manager.js";
|
||||||
@@ -110,6 +111,12 @@ const PROVIDER_CLIENT_FACTORIES: Record<string, ProviderClientFactory> = {
|
|||||||
logger,
|
logger,
|
||||||
runtimeSettings,
|
runtimeSettings,
|
||||||
}),
|
}),
|
||||||
|
cursor: (logger, runtimeSettings) =>
|
||||||
|
new CursorACPAgentClient({
|
||||||
|
logger,
|
||||||
|
command: getCursorACPCommand(runtimeSettings),
|
||||||
|
env: runtimeSettings?.env,
|
||||||
|
}),
|
||||||
opencode: (logger, runtimeSettings) => new OpenCodeAgentClient(logger, runtimeSettings),
|
opencode: (logger, runtimeSettings) => new OpenCodeAgentClient(logger, runtimeSettings),
|
||||||
pi: (logger, runtimeSettings) =>
|
pi: (logger, runtimeSettings) =>
|
||||||
new PiDirectAgentClient({
|
new PiDirectAgentClient({
|
||||||
@@ -119,6 +126,19 @@ const PROVIDER_CLIENT_FACTORIES: Record<string, ProviderClientFactory> = {
|
|||||||
mock: (logger) => new MockLoadTestAgentClient(logger),
|
mock: (logger) => new MockLoadTestAgentClient(logger),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getCursorACPCommand(
|
||||||
|
runtimeSettings: ProviderRuntimeSettings | undefined,
|
||||||
|
): [string, ...string[]] {
|
||||||
|
if (
|
||||||
|
runtimeSettings?.command?.mode === "replace" &&
|
||||||
|
isNonEmptyStringArray(runtimeSettings.command.argv)
|
||||||
|
) {
|
||||||
|
return runtimeSettings.command.argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["cursor-agent", "acp"];
|
||||||
|
}
|
||||||
|
|
||||||
function getProviderClientFactory(provider: string): ProviderClientFactory {
|
function getProviderClientFactory(provider: string): ProviderClientFactory {
|
||||||
const factory = PROVIDER_CLIENT_FACTORIES[provider];
|
const factory = PROVIDER_CLIENT_FACTORIES[provider];
|
||||||
if (!factory) {
|
if (!factory) {
|
||||||
@@ -513,13 +533,21 @@ function addDerivedProviders(
|
|||||||
enabled: override.enabled !== false,
|
enabled: override.enabled !== false,
|
||||||
derivedFromProviderId: null,
|
derivedFromProviderId: null,
|
||||||
createBaseClient: (logger) =>
|
createBaseClient: (logger) =>
|
||||||
new GenericACPAgentClient({
|
providerId === "cursor"
|
||||||
logger,
|
? new CursorACPAgentClient({
|
||||||
command,
|
logger,
|
||||||
env: override.env,
|
command,
|
||||||
providerId,
|
env: override.env,
|
||||||
label: override.label ?? providerId,
|
providerId,
|
||||||
}),
|
label: override.label ?? providerId,
|
||||||
|
})
|
||||||
|
: new GenericACPAgentClient({
|
||||||
|
logger,
|
||||||
|
command,
|
||||||
|
env: override.env,
|
||||||
|
providerId,
|
||||||
|
label: override.label ?? providerId,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { SpawnedACPProcess, SessionStateResponse } from "./acp-agent.js";
|
||||||
|
import { CursorACPAgentClient, parseCursorAgentModelsOutput } from "./cursor-acp-agent.js";
|
||||||
|
import { createTestLogger } from "../../../test-utils/test-logger.js";
|
||||||
|
import * as spawnUtils from "../../../utils/spawn.js";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseCursorAgentModelsOutput", () => {
|
||||||
|
test("parses Cursor model list output and marks default model", () => {
|
||||||
|
expect(
|
||||||
|
parseCursorAgentModelsOutput(`
|
||||||
|
Available models
|
||||||
|
|
||||||
|
auto - Auto
|
||||||
|
composer-2-fast - Composer 2 Fast (default)
|
||||||
|
gpt-5.5-low - GPT-5.5 1M Low (current)
|
||||||
|
|
||||||
|
Tip: use --model <id> (or /model <id> in interactive mode) to switch.
|
||||||
|
`),
|
||||||
|
).toEqual([
|
||||||
|
{ provider: "acp", id: "auto", label: "Auto", isDefault: false },
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "composer-2-fast",
|
||||||
|
label: "Composer 2 Fast",
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "gpt-5.5-low",
|
||||||
|
label: "GPT-5.5 1M Low",
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to first model as default when Cursor marks none", () => {
|
||||||
|
expect(
|
||||||
|
parseCursorAgentModelsOutput(`
|
||||||
|
Available models
|
||||||
|
composer-2 - Composer 2
|
||||||
|
gpt-5.5-low - GPT-5.5 1M Low
|
||||||
|
`),
|
||||||
|
).toEqual([
|
||||||
|
{ provider: "acp", id: "composer-2", label: "Composer 2", isDefault: true },
|
||||||
|
{ provider: "acp", id: "gpt-5.5-low", label: "GPT-5.5 1M Low", isDefault: false },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CursorACPAgentClient model fallback", () => {
|
||||||
|
class TestCursorACPAgentClient extends CursorACPAgentClient {
|
||||||
|
constructor(options: {
|
||||||
|
command?: [string, ...string[]];
|
||||||
|
env?: Record<string, string>;
|
||||||
|
response: SessionStateResponse;
|
||||||
|
}) {
|
||||||
|
super({
|
||||||
|
logger: createTestLogger(),
|
||||||
|
command: options.command ?? ["cursor-agent", "acp"],
|
||||||
|
env: options.env,
|
||||||
|
});
|
||||||
|
this.response = options.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly response: SessionStateResponse;
|
||||||
|
|
||||||
|
protected override async spawnProcess(): Promise<SpawnedACPProcess> {
|
||||||
|
return {
|
||||||
|
child: { kill: vi.fn(), exitCode: 0, signalCode: null, once: vi.fn() },
|
||||||
|
connection: {
|
||||||
|
newSession: vi.fn().mockResolvedValue(this.response),
|
||||||
|
},
|
||||||
|
initialize: { agentCapabilities: {} },
|
||||||
|
} as SpawnedACPProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async closeProbe(): Promise<void> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("uses cursor-agent models when Cursor ACP reports zero models", async () => {
|
||||||
|
const execCommand = vi.spyOn(spawnUtils, "execCommand").mockResolvedValue({
|
||||||
|
stdout: "Available models\ncomposer-2-fast - Composer 2 Fast (default)\n",
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
response: { sessionId: "session-1", models: null, configOptions: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const models = await client.listModels({ cwd: "/tmp/cursor", force: false });
|
||||||
|
|
||||||
|
expect(models).toEqual([
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "composer-2-fast",
|
||||||
|
label: "Composer 2 Fast",
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(execCommand).toHaveBeenCalledWith(
|
||||||
|
"cursor-agent",
|
||||||
|
["models"],
|
||||||
|
expect.objectContaining({ timeout: expect.any(Number) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses cursor-agent models for absolute cursor-agent commands", async () => {
|
||||||
|
const execCommand = vi.spyOn(spawnUtils, "execCommand").mockResolvedValue({
|
||||||
|
stdout: "Available models\ncomposer-2-fast - Composer 2 Fast (default)\n",
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
command: ["/opt/cursor/bin/cursor-agent", "acp"],
|
||||||
|
response: { sessionId: "session-1", models: null, configOptions: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(client.listModels({ cwd: "/tmp/cursor", force: false })).resolves.toEqual([
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "composer-2-fast",
|
||||||
|
label: "Composer 2 Fast",
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(execCommand).toHaveBeenCalledWith(
|
||||||
|
"/opt/cursor/bin/cursor-agent",
|
||||||
|
["models"],
|
||||||
|
expect.objectContaining({ timeout: expect.any(Number) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("passes Cursor provider env to cursor-agent models fallback", async () => {
|
||||||
|
const execCommand = vi.spyOn(spawnUtils, "execCommand").mockResolvedValue({
|
||||||
|
stdout: "Available models\ncomposer-2-fast - Composer 2 Fast (default)\n",
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const env = { CURSOR_AGENT_LOG: "debug" };
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
env,
|
||||||
|
response: { sessionId: "session-1", models: null, configOptions: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.listModels({ cwd: "/tmp/cursor", force: false });
|
||||||
|
|
||||||
|
expect(execCommand).toHaveBeenCalledWith(
|
||||||
|
"cursor-agent",
|
||||||
|
["models"],
|
||||||
|
expect.objectContaining({ envOverlay: env }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not run fallback when ACP returns models", async () => {
|
||||||
|
const execCommand = vi.spyOn(spawnUtils, "execCommand");
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
response: {
|
||||||
|
sessionId: "session-1",
|
||||||
|
models: {
|
||||||
|
currentModelId: "acp-model",
|
||||||
|
availableModels: [{ modelId: "acp-model", name: "ACP Model", description: null }],
|
||||||
|
},
|
||||||
|
configOptions: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(client.listModels({ cwd: "/tmp/cursor", force: false })).resolves.toEqual([
|
||||||
|
{
|
||||||
|
provider: "acp",
|
||||||
|
id: "acp-model",
|
||||||
|
label: "ACP Model",
|
||||||
|
description: undefined,
|
||||||
|
isDefault: true,
|
||||||
|
thinkingOptions: undefined,
|
||||||
|
defaultThinkingOptionId: undefined,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(execCommand).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not run fallback when command is not cursor-agent", async () => {
|
||||||
|
const execCommand = vi.spyOn(spawnUtils, "execCommand");
|
||||||
|
const client = new TestCursorACPAgentClient({
|
||||||
|
command: ["other-agent", "acp"],
|
||||||
|
response: { sessionId: "session-1", models: null, configOptions: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(client.listModels({ cwd: "/tmp/cursor", force: false })).resolves.toEqual([]);
|
||||||
|
expect(execCommand).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { basename } from "node:path";
|
||||||
|
import type { Logger } from "pino";
|
||||||
|
|
||||||
|
import type { AgentModelDefinition, ListModelsOptions } from "../agent-sdk-types.js";
|
||||||
|
import * as spawnUtils from "../../../utils/spawn.js";
|
||||||
|
import { GenericACPAgentClient } from "./generic-acp-agent.js";
|
||||||
|
|
||||||
|
interface CursorACPAgentClientOptions {
|
||||||
|
logger: Logger;
|
||||||
|
command: [string, ...string[]];
|
||||||
|
env?: Record<string, string>;
|
||||||
|
providerId?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CURSOR_MODELS_TIMEOUT_MS = 10_000;
|
||||||
|
const CURSOR_MODEL_MARKER_PATTERN = /\s+\((?:default|current)\)$/;
|
||||||
|
|
||||||
|
export class CursorACPAgentClient extends GenericACPAgentClient {
|
||||||
|
private readonly cursorCommand: [string, ...string[]];
|
||||||
|
private readonly env?: Record<string, string>;
|
||||||
|
|
||||||
|
constructor(options: CursorACPAgentClientOptions) {
|
||||||
|
super(options);
|
||||||
|
this.cursorCommand = options.command;
|
||||||
|
this.env = options.env;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async listModels(options: ListModelsOptions): Promise<AgentModelDefinition[]> {
|
||||||
|
const acpModels = await super.listModels(options);
|
||||||
|
if (acpModels.length > 0) {
|
||||||
|
return acpModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.canUseCursorModelsFallback()) {
|
||||||
|
return this.listCursorModelsFallback(acpModels);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acpModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private canUseCursorModelsFallback(): boolean {
|
||||||
|
return basename(this.cursorCommand[0]) === "cursor-agent";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async listCursorModelsFallback(
|
||||||
|
acpModels: AgentModelDefinition[],
|
||||||
|
): Promise<AgentModelDefinition[]> {
|
||||||
|
try {
|
||||||
|
const { stdout } = await spawnUtils.execCommand(this.cursorCommand[0], ["models"], {
|
||||||
|
envOverlay: this.env,
|
||||||
|
timeout: CURSOR_MODELS_TIMEOUT_MS,
|
||||||
|
maxBuffer: 1024 * 1024,
|
||||||
|
});
|
||||||
|
const fallbackModels = parseCursorAgentModelsOutput(stdout);
|
||||||
|
if (fallbackModels.length === 0) {
|
||||||
|
this.logger.warn(
|
||||||
|
{ provider: "cursor" },
|
||||||
|
"Cursor ACP model fallback returned no parseable models",
|
||||||
|
);
|
||||||
|
return acpModels;
|
||||||
|
}
|
||||||
|
return fallbackModels;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
{ err: error, provider: "cursor" },
|
||||||
|
"Failed to list Cursor models via cursor-agent models fallback",
|
||||||
|
);
|
||||||
|
return acpModels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedCursorModel {
|
||||||
|
provider: "acp";
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
marker: "default" | "current" | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseCursorAgentModelsOutput(output: string): AgentModelDefinition[] {
|
||||||
|
const parsed = output
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line && line !== "Available models" && !line.startsWith("Tip:"))
|
||||||
|
.map((line) => {
|
||||||
|
const separatorIndex = line.indexOf(" - ");
|
||||||
|
if (separatorIndex <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = line.slice(0, separatorIndex).trim();
|
||||||
|
const rawLabel = line.slice(separatorIndex + 3).trim();
|
||||||
|
if (!id || !rawLabel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker: "default" | "current" | null = null;
|
||||||
|
if (rawLabel.endsWith(" (default)")) {
|
||||||
|
marker = "default";
|
||||||
|
} else if (rawLabel.endsWith(" (current)")) {
|
||||||
|
marker = "current";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
provider: "acp",
|
||||||
|
id,
|
||||||
|
label: rawLabel.replace(CURSOR_MODEL_MARKER_PATTERN, ""),
|
||||||
|
marker,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((model): model is ParsedCursorModel => model !== null);
|
||||||
|
|
||||||
|
const defaultModelId =
|
||||||
|
parsed.find((model) => model.marker === "default")?.id ??
|
||||||
|
parsed.find((model) => model.marker === "current")?.id ??
|
||||||
|
parsed[0]?.id;
|
||||||
|
|
||||||
|
return parsed.map((model) => ({
|
||||||
|
provider: model.provider,
|
||||||
|
id: model.id,
|
||||||
|
label: model.label,
|
||||||
|
isDefault: model.id === defaultModelId,
|
||||||
|
}));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user