coder(providers): remove retired cursor and copilot providers
Drop both retired providers from BooCoder's provider layer: acp-spawn argv cases, provider-manifest mode blocks + manifest keys, provider-commands maps, the provider-snapshot cursor model-CLI branch (+ orphaned exec/promisify imports), the agent-probe copilot ACP-detect branch, and the now-dead cursor-models module + its test. The PROVIDERS registry array already lacked both. Built-ins unchanged: claude, opencode, goose, qwen, native boocode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseCursorAgentModelsOutput } from '../cursor-models.js';
|
||||
|
||||
describe('parseCursorAgentModelsOutput', () => {
|
||||
it('parses cursor-agent models output with default marker', () => {
|
||||
const output = `
|
||||
Available models
|
||||
claude-4-sonnet - Claude 4 Sonnet (default)
|
||||
gpt-4.1 - GPT-4.1
|
||||
Tip: use cursor-agent models for full list
|
||||
`.trim();
|
||||
|
||||
const models = parseCursorAgentModelsOutput(output);
|
||||
|
||||
expect(models).toEqual([
|
||||
{ id: 'claude-4-sonnet', label: 'Claude 4 Sonnet', isDefault: true },
|
||||
{ id: 'gpt-4.1', label: 'GPT-4.1', isDefault: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses current marker when no default', () => {
|
||||
const output = `
|
||||
model-a - Model A (current)
|
||||
model-b - Model B
|
||||
`.trim();
|
||||
|
||||
const models = parseCursorAgentModelsOutput(output);
|
||||
|
||||
expect(models.find((m) => m.id === 'model-a')?.isDefault).toBe(true);
|
||||
expect(models.find((m) => m.id === 'model-b')?.isDefault).toBe(false);
|
||||
});
|
||||
|
||||
it('defaults to first model when no markers', () => {
|
||||
const output = 'alpha - Alpha\nbeta - Beta';
|
||||
const models = parseCursorAgentModelsOutput(output);
|
||||
|
||||
expect(models[0]?.isDefault).toBe(true);
|
||||
expect(models[1]?.isDefault).toBe(false);
|
||||
});
|
||||
|
||||
it('skips malformed lines', () => {
|
||||
const output = 'no-separator\nvalid - Valid';
|
||||
const models = parseCursorAgentModelsOutput(output);
|
||||
|
||||
expect(models).toEqual([{ id: 'valid', label: 'Valid', isDefault: true }]);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { getManifestCommands, mergeCommands, PROVIDER_COMMANDS } from '../provid
|
||||
|
||||
describe('provider-commands', () => {
|
||||
it('defines commands for every external harness', () => {
|
||||
for (const name of ['claude', 'opencode', 'cursor', 'goose', 'qwen', 'copilot']) {
|
||||
for (const name of ['claude', 'opencode', 'goose', 'qwen']) {
|
||||
expect(getManifestCommands(name).length, name).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,10 +6,6 @@ export function resolveAcpSpawnArgs(agent: string): string[] | null {
|
||||
case 'opencode':
|
||||
case 'goose':
|
||||
return ['acp'];
|
||||
case 'cursor':
|
||||
return ['acp'];
|
||||
case 'copilot':
|
||||
return ['--acp'];
|
||||
case 'qwen':
|
||||
return ['--acp'];
|
||||
default:
|
||||
@@ -18,12 +14,5 @@ export function resolveAcpSpawnArgs(agent: string): string[] | null {
|
||||
}
|
||||
|
||||
export function resolveAcpProbeBinaries(agent: string): string[] {
|
||||
switch (agent) {
|
||||
case 'cursor':
|
||||
return ['cursor-agent', 'agent'];
|
||||
case 'copilot':
|
||||
return ['copilot'];
|
||||
default:
|
||||
return [agent];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,6 @@ async function detectAcpSupport(agentName: string, installPath: string): Promise
|
||||
const transport = PROVIDERS_BY_NAME.get(agentName)?.transport;
|
||||
if (transport !== 'acp') return false;
|
||||
|
||||
if (agentName === 'copilot') {
|
||||
try {
|
||||
const { stdout } = await exec(`"${installPath}" --help`, { timeout: 10_000 });
|
||||
return stdout.includes('--acp');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (agentName === 'qwen') {
|
||||
try {
|
||||
const { stdout } = await exec(`"${installPath}" --help`, { timeout: 10_000 });
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Cursor model list parser — lifted from Paseo cursor-acp-agent.ts
|
||||
*/
|
||||
import type { ProviderModel } from './provider-types.js';
|
||||
|
||||
const CURSOR_MODEL_MARKER_PATTERN = /\s+\((?:default|current)\)$/;
|
||||
|
||||
export function parseCursorAgentModelsOutput(output: string): ProviderModel[] {
|
||||
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 { id, label: rawLabel.replace(CURSOR_MODEL_MARKER_PATTERN, ''), marker };
|
||||
})
|
||||
.filter((m): m is { id: string; label: string; marker: 'default' | 'current' | null } => m !== null);
|
||||
|
||||
const defaultModelId =
|
||||
parsed.find((m) => m.marker === 'default')?.id ??
|
||||
parsed.find((m) => m.marker === 'current')?.id ??
|
||||
parsed[0]?.id;
|
||||
|
||||
return parsed.map((model) => ({
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
isDefault: model.id === defaultModelId,
|
||||
}));
|
||||
}
|
||||
@@ -27,13 +27,6 @@ const OPENCODE_COMMANDS: AgentCommand[] = [
|
||||
{ name: 'export', description: 'Export session' },
|
||||
];
|
||||
|
||||
const CURSOR_COMMANDS: AgentCommand[] = [
|
||||
{ name: 'help', description: 'Show available slash commands' },
|
||||
{ name: 'clear', description: 'Clear conversation' },
|
||||
{ name: 'compact', description: 'Compact context' },
|
||||
{ name: 'resume', description: 'Resume a prior session' },
|
||||
];
|
||||
|
||||
const GOOSE_COMMANDS: AgentCommand[] = [
|
||||
{ name: 'help', description: 'Show available commands' },
|
||||
{ name: 'clear', description: 'Clear conversation' },
|
||||
@@ -49,23 +42,12 @@ const QWEN_COMMANDS: AgentCommand[] = [
|
||||
{ name: 'review', description: 'Review changes' },
|
||||
];
|
||||
|
||||
const COPILOT_COMMANDS: AgentCommand[] = [
|
||||
{ name: 'help', description: 'Show available commands' },
|
||||
{ name: 'explain', description: 'Explain selected code' },
|
||||
{ name: 'fix', description: 'Fix issues in context' },
|
||||
{ name: 'tests', description: 'Generate or run tests' },
|
||||
{ name: 'doc', description: 'Generate documentation' },
|
||||
{ name: 'clear', description: 'Clear conversation' },
|
||||
];
|
||||
|
||||
/** boocode harness uses /api/skills — merged on the frontend. */
|
||||
export const PROVIDER_COMMANDS: Record<string, AgentCommand[]> = {
|
||||
claude: CLAUDE_COMMANDS,
|
||||
opencode: OPENCODE_COMMANDS,
|
||||
cursor: CURSOR_COMMANDS,
|
||||
goose: GOOSE_COMMANDS,
|
||||
qwen: QWEN_COMMANDS,
|
||||
copilot: COPILOT_COMMANDS,
|
||||
boocode: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -24,31 +24,6 @@ const OPENCODE_MODES: ProviderMode[] = [
|
||||
{ id: 'full-access', label: 'Full Access', description: 'Auto-approves all tool prompts', isUnattended: true },
|
||||
];
|
||||
|
||||
const COPILOT_MODES: ProviderMode[] = [
|
||||
{
|
||||
id: 'https://agentclientprotocol.com/protocol/session-modes#agent',
|
||||
label: 'Agent',
|
||||
description: 'Default agent mode',
|
||||
},
|
||||
{
|
||||
id: 'https://agentclientprotocol.com/protocol/session-modes#plan',
|
||||
label: 'Plan',
|
||||
description: 'Plan mode for multi-step work',
|
||||
},
|
||||
{
|
||||
id: 'allow-all',
|
||||
label: 'Allow All',
|
||||
description: 'Automatically approves all tool, path, and URL requests',
|
||||
isUnattended: true,
|
||||
},
|
||||
];
|
||||
|
||||
const CURSOR_CLI_MODES: ProviderMode[] = [
|
||||
{ id: 'agent', label: 'Agent', description: 'Full agent capabilities with tool access' },
|
||||
{ id: 'plan', label: 'Plan', description: 'Read-only planning mode' },
|
||||
{ id: 'ask', label: 'Ask', description: 'Q&A read-only mode' },
|
||||
];
|
||||
|
||||
const QWEN_PTY_MODES: ProviderMode[] = [
|
||||
{ id: 'default', label: 'Default', description: 'Prompt for approval' },
|
||||
{ id: 'plan', label: 'Plan', description: 'Plan only — no edits' },
|
||||
@@ -75,14 +50,6 @@ export const PROVIDER_MANIFEST: Record<string, ProviderManifestEntry> = {
|
||||
defaultModeId: 'build',
|
||||
modes: OPENCODE_MODES,
|
||||
},
|
||||
copilot: {
|
||||
defaultModeId: 'https://agentclientprotocol.com/protocol/session-modes#agent',
|
||||
modes: COPILOT_MODES,
|
||||
},
|
||||
cursor: {
|
||||
defaultModeId: 'agent',
|
||||
modes: CURSOR_CLI_MODES,
|
||||
},
|
||||
goose: {
|
||||
defaultModeId: null,
|
||||
modes: [],
|
||||
|
||||
@@ -13,8 +13,7 @@ export interface ProviderDef {
|
||||
* - boocode: llama-swap only
|
||||
* - opencode: ACP probe + mergeLlamaSwap (prefixed llama-swap/* ids)
|
||||
* - qwen: ACP probe + merge ~/.qwen/settings.json; PTY fallback reads settings only
|
||||
* - cursor: ACP probe + cursor-agent models CLI fallback
|
||||
* - goose / copilot: ACP probe only
|
||||
* - goose: ACP probe only
|
||||
* - claude: static manifest models + thinking options
|
||||
*/
|
||||
export const PROVIDERS: ProviderDef[] = [
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
* Provider snapshot cache — cold ACP probe per provider + static manifest merge.
|
||||
*/
|
||||
import { homedir } from 'node:os';
|
||||
import { exec as execCb } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import type { FastifyBaseLogger } from 'fastify';
|
||||
import type { Sql } from '../db.js';
|
||||
import type { Config } from '../config.js';
|
||||
@@ -14,13 +12,10 @@ import {
|
||||
PROVIDER_MANIFEST,
|
||||
} from './provider-manifest.js';
|
||||
import { probeAcpProvider } from './acp-probe.js';
|
||||
import { parseCursorAgentModelsOutput } from './cursor-models.js';
|
||||
import type { ProviderModel, ProviderSnapshotEntry } from './provider-types.js';
|
||||
import { getManifestCommands, mergeCommands } from './provider-commands.js';
|
||||
import { readQwenSettingsModels } from './qwen-settings.js';
|
||||
|
||||
const exec = promisify(execCb);
|
||||
|
||||
interface AgentRow {
|
||||
name: string;
|
||||
install_path: string | null;
|
||||
@@ -41,15 +36,6 @@ async function fetchLlamaSwapModels(config: Config): Promise<ProviderModel[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCursorModelsCli(installPath: string): Promise<ProviderModel[]> {
|
||||
try {
|
||||
const { stdout } = await exec(`"${installPath}" models`, { timeout: 15_000, maxBuffer: 1024 * 1024 });
|
||||
return parseCursorAgentModelsOutput(stdout);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefix llama-swap model ids so they don't collide with provider-native models. */
|
||||
export function prefixLlamaSwapModels(models: ProviderModel[]): ProviderModel[] {
|
||||
return models.map((m) => ({
|
||||
@@ -141,8 +127,6 @@ async function buildProviderEntry(
|
||||
const probe = await probeAcpProvider(provider.name, agentRow.install_path, cwd);
|
||||
if (probe.models.length > 0) {
|
||||
models = probe.models;
|
||||
} else if (provider.name === 'cursor' && agentRow.install_path) {
|
||||
models = await fetchCursorModelsCli(agentRow.install_path);
|
||||
} else if (provider.modelSource === 'llama-swap') {
|
||||
models = llamaModels;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user