diff --git a/packages/app/src/data/acp-provider-catalog.ts b/packages/app/src/data/acp-provider-catalog.ts index cd2c6458c..fb3059620 100644 --- a/packages/app/src/data/acp-provider-catalog.ts +++ b/packages/app/src/data/acp-provider-catalog.ts @@ -341,7 +341,7 @@ const CATALOG_DATA = [ description: "ByteDance's official TRAE coding agent with native ACP support", version: "manual", iconId: "traecli", - installLink: "https://docs.trae.cn/cli", + installLink: "https://docs.trae.cn/cli_get-started-with-trae-cli", command: ["traecli", "acp", "serve"], }, { diff --git a/packages/server/src/server/agent/provider-registry.test.ts b/packages/server/src/server/agent/provider-registry.test.ts index 27e62a1a7..877680c9e 100644 --- a/packages/server/src/server/agent/provider-registry.test.ts +++ b/packages/server/src/server/agent/provider-registry.test.ts @@ -25,6 +25,11 @@ const mockState = vi.hoisted(() => { env?: Record; providerParams?: unknown; }>, + trae: [] as Array<{ + command: string[]; + env?: Record; + providerParams?: unknown; + }>, pi: [] as ConstructorEntry[], genericAcp: [] as Array<{ command: string[]; @@ -41,6 +46,7 @@ const mockState = vi.hoisted(() => { this.constructorArgs.codex = []; this.constructorArgs.copilot = []; this.constructorArgs.cursor = []; + this.constructorArgs.trae = []; this.constructorArgs.pi = []; this.constructorArgs.genericAcp = []; this.isCommandAvailable.mockReset(); @@ -375,6 +381,59 @@ vi.mock("./providers/cursor-acp-agent.js", () => ({ }, })); +vi.mock("./providers/trae-acp-agent.js", () => ({ + TraeACPAgentClient: class TraeACPAgentClient { + 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; + providerParams?: unknown; + }) { + this.runtimeSettings = { + command: { + mode: "replace", + argv: options.command, + }, + env: options.env, + }; + mockState.constructorArgs.trae.push({ + command: options.command, + env: options.env, + providerParams: options.providerParams, + }); + } + + async createSession(): Promise { + throw new Error("not implemented"); + } + + async resumeSession(): Promise { + throw new Error("not implemented"); + } + + async fetchCatalog(): Promise { + return { + models: mockState.runtimeModels.get(this.provider) ?? [], + modes: [], + }; + } + + async isAvailable(): Promise { + return true; + } + }, +})); + import { AGENT_PROVIDER_DEFINITIONS, buildProviderRegistry, @@ -638,6 +697,33 @@ test("cursor provider extending acp uses CursorACPAgentClient", () => { expect(mockState.constructorArgs.genericAcp).toEqual([]); }); +test("traecli provider extending acp uses TraeACPAgentClient", () => { + const registry = buildProviderRegistry(logger, { + providerOverrides: { + traecli: { + extends: "acp", + label: "TRAE CLI", + command: ["traecli", "acp", "serve"], + }, + }, + }); + + expect(registry.traecli.createClient(logger).provider).toBe("traecli"); + expect(mockState.constructorArgs.trae).toEqual([ + { + command: ["traecli", "acp", "serve"], + env: undefined, + providerParams: undefined, + }, + { + command: ["traecli", "acp", "serve"], + env: undefined, + providerParams: undefined, + }, + ]); + expect(mockState.constructorArgs.genericAcp).toEqual([]); +}); + test('extends: "acp" without command throws', () => { expect(() => buildProviderRegistry(logger, { diff --git a/packages/server/src/server/agent/provider-registry.ts b/packages/server/src/server/agent/provider-registry.ts index 42382cb0c..a582e1625 100644 --- a/packages/server/src/server/agent/provider-registry.ts +++ b/packages/server/src/server/agent/provider-registry.ts @@ -36,6 +36,7 @@ import { GenericACPAgentClient } from "./providers/generic-acp-agent.js"; import { KiroACPAgentClient } from "./providers/kiro-acp-agent.js"; import { OpenCodeAgentClient } from "./providers/opencode-agent.js"; import { PiRpcAgentClient } from "./providers/pi/agent.js"; +import { TraeACPAgentClient } from "./providers/trae-acp-agent.js"; import { MockLoadTestAgentClient } from "./providers/mock-load-test-agent.js"; import { MockSlowProviderClient } from "./providers/mock-slow-provider.js"; import { @@ -660,6 +661,9 @@ function addDerivedProviders( if (providerId === "kiro") { return new KiroACPAgentClient(acpOptions); } + if (providerId === "traecli") { + return new TraeACPAgentClient(acpOptions); + } return new GenericACPAgentClient(acpOptions); }, }); diff --git a/packages/server/src/server/agent/providers/trae-acp-agent.ts b/packages/server/src/server/agent/providers/trae-acp-agent.ts new file mode 100644 index 000000000..b1e0893e4 --- /dev/null +++ b/packages/server/src/server/agent/providers/trae-acp-agent.ts @@ -0,0 +1,30 @@ +import type { Logger } from "pino"; + +import { GenericACPAgentClient } from "./generic-acp-agent.js"; + +interface TraeACPAgentClientOptions { + logger: Logger; + command: [string, ...string[]]; + env?: Record; + providerId?: string; + label?: string; + providerParams?: unknown; +} + +const TRAE_INITIAL_COMMANDS_WAIT_TIMEOUT_MS = 10_000; + +export class TraeACPAgentClient extends GenericACPAgentClient { + constructor(options: TraeACPAgentClientOptions) { + super({ + logger: options.logger, + command: options.command, + env: options.env, + providerId: options.providerId, + label: options.label, + providerParams: options.providerParams, + // traecli publishes slash commands and skills asynchronously via available_commands_update. + waitForInitialCommands: true, + initialCommandsWaitTimeoutMs: TRAE_INITIAL_COMMANDS_WAIT_TIMEOUT_MS, + }); + } +} diff --git a/public-docs/supported-providers.md b/public-docs/supported-providers.md index 57462f38d..ff0bb0b58 100644 --- a/public-docs/supported-providers.md +++ b/public-docs/supported-providers.md @@ -56,7 +56,7 @@ Pick any of these from the in-app provider catalog. Each entry is a one-click in - [Qwen Code](https://qwenlm.github.io/qwen-code-docs/en/users/overview), Alibaba's Qwen coding assistant. - [siGit Code](https://github.com/getsigit/sigit), local-first coding agent with optional on-device LLM. - [Stakpak](https://stakpak.dev/), Rust-based DevOps agent. -- [TRAE CLI](https://docs.trae.cn/cli), ByteDance's official TRAE coding agent. +- [TRAE CLI](https://docs.trae.cn/cli_get-started-with-trae-cli), ByteDance's official TRAE coding agent. - [VT Code](https://github.com/vinhnx/VTCode/blob/main/docs/guides/zed-acp.md), open-source multi-provider coding agent. The in-app catalog is the canonical, version-pinned source. Anything not listed here can still be added manually, see [Custom providers](/docs/custom-providers).