From 9ef7230417427f8872929f90c66eb118f723da30 Mon Sep 17 00:00:00 2001 From: Mingyang Sun Date: Tue, 19 May 2026 11:23:57 +0800 Subject: [PATCH] Add Kiro CLI to ACP provider catalog Adds Kiro CLI as an opt-in ACP provider catalog entry, plus a generic ACP extension-notification sink so unknown notifications no longer fail JSON-RPC dispatch. --- packages/app/src/components/provider-icons.ts | 3 ++- packages/app/src/data/acp-provider-catalog.ts | 9 +++++++++ .../hooks/use-acp-provider-catalog.test.ts | 3 ++- .../server/agent/providers/acp-agent.test.ts | 20 +++++++++++++++++++ .../src/server/agent/providers/acp-agent.ts | 13 ++++++++++++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/provider-icons.ts b/packages/app/src/components/provider-icons.ts index 6c8a689c5..1a85cf97f 100644 --- a/packages/app/src/components/provider-icons.ts +++ b/packages/app/src/components/provider-icons.ts @@ -1,4 +1,4 @@ -import { Bot } from "lucide-react-native"; +import { Bot, PackagePlus } from "lucide-react-native"; import { ClaudeIcon } from "@/components/icons/claude-icon"; import { CodexIcon } from "@/components/icons/codex-icon"; import { CopilotIcon } from "@/components/icons/copilot-icon"; @@ -9,6 +9,7 @@ const PROVIDER_ICONS: Record = { claude: ClaudeIcon as unknown as typeof Bot, codex: CodexIcon as unknown as typeof Bot, copilot: CopilotIcon as unknown as typeof Bot, + kiro: PackagePlus, opencode: OpenCodeIcon as unknown as typeof Bot, pi: PiIcon as unknown as typeof Bot, }; diff --git a/packages/app/src/data/acp-provider-catalog.ts b/packages/app/src/data/acp-provider-catalog.ts index 74c819598..85895109c 100644 --- a/packages/app/src/data/acp-provider-catalog.ts +++ b/packages/app/src/data/acp-provider-catalog.ts @@ -240,6 +240,15 @@ const CATALOG_DATA = [ installLink: "https://kilo.ai/docs/code-with-ai/platforms/cli", command: ["kilo", "acp"], }, + { + id: "kiro", + title: "Kiro CLI", + description: "Amazon's AI coding agent with native ACP support", + version: "manual", + iconId: null, + installLink: "https://kiro.dev/docs/cli/acp/", + command: ["kiro-cli", "acp"], + }, { id: "kimi", title: "Kimi CLI", diff --git a/packages/app/src/hooks/use-acp-provider-catalog.test.ts b/packages/app/src/hooks/use-acp-provider-catalog.test.ts index d96fcc5bf..13ff91a1a 100644 --- a/packages/app/src/hooks/use-acp-provider-catalog.test.ts +++ b/packages/app/src/hooks/use-acp-provider-catalog.test.ts @@ -26,7 +26,7 @@ describe("ACP provider catalog", () => { }); it("bundles SVG icons for catalog entries that declare an icon", () => { - const entriesWithIcons = ACP_PROVIDER_CATALOG.filter((entry) => entry.id !== "hermes"); + const entriesWithIcons = ACP_PROVIDER_CATALOG.filter((entry) => entry.iconSvg !== null); expect(entriesWithIcons.length).toBeGreaterThan(0); for (const entry of entriesWithIcons) { @@ -39,6 +39,7 @@ describe("ACP provider catalog", () => { expect(findProvider("cursor").command).toEqual(["cursor-agent", "acp"]); expect(findProvider("goose").command).toEqual(["goose", "acp"]); expect(findProvider("junie").command).toEqual(["junie", "--acp", "true"]); + expect(findProvider("kiro").command).toEqual(["kiro-cli", "acp"]); expect(findProvider("poolside").command).toEqual(["pool", "acp"]); }); diff --git a/packages/server/src/server/agent/providers/acp-agent.test.ts b/packages/server/src/server/agent/providers/acp-agent.test.ts index 0cbc2a4df..1f3e9fe30 100644 --- a/packages/server/src/server/agent/providers/acp-agent.test.ts +++ b/packages/server/src/server/agent/providers/acp-agent.test.ts @@ -1438,6 +1438,26 @@ describe("ACPAgentSession slash commands", () => { }); describe("ACPAgentSession", () => { + test("accepts ACP extension notifications without failing the JSON-RPC connection", async () => { + const logger = createTestLogger(); + const trace = vi.spyOn(logger, "trace"); + const session = createSessionWithConfig({ provider: "kiro" }, logger); + + await expect( + session.extNotification("_kiro.dev/session/initialized", { + sessionId: "session-1", + }), + ).resolves.toBeUndefined(); + expect(trace).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "kiro", + method: "_kiro.dev/session/initialized", + sessionId: "session-1", + }), + "provider.acp.extension_notification", + ); + }); + test("emits assistant and reasoning chunks as deltas while user chunks stay accumulated", async () => { const session = createSession(); const events: Array<{ type: string; item?: { type: string; text?: string } }> = []; diff --git a/packages/server/src/server/agent/providers/acp-agent.ts b/packages/server/src/server/agent/providers/acp-agent.ts index a9f7924c5..dbf2e3d02 100644 --- a/packages/server/src/server/agent/providers/acp-agent.ts +++ b/packages/server/src/server/agent/providers/acp-agent.ts @@ -1667,6 +1667,19 @@ export class ACPAgentSession implements AgentSession, ACPClient { } } + async extNotification(method: string, params: Record): Promise { + this.logger.trace( + { + agentId: this.agentId, + provider: this.provider, + sessionId: typeof params.sessionId === "string" ? params.sessionId : undefined, + method, + rawEvent: params, + }, + "provider.acp.extension_notification", + ); + } + async readTextFile(params: ReadTextFileRequest): Promise<{ content: string }> { const raw = await fs.readFile(params.path, "utf8"); if (!params.line && !params.limit) {