Compare commits

...

1 Commits

Author SHA1 Message Date
Mohamed Boudra
6983ca322d Add Antigravity CLI to ACP catalog 2026-05-20 10:57:29 +07:00
9 changed files with 279 additions and 1 deletions

View File

@@ -339,7 +339,7 @@ The [Agent Client Protocol (ACP)](https://agentclientprotocol.com) is an open st
ACP agents communicate over JSON-RPC 2.0 on stdio. Paseo spawns the agent process and talks to it through stdin/stdout.
Paseo also ships an in-app ACP provider catalog for common agents, including Cursor, DeepAgents, DeepSeek TUI, DimCode, Gemini CLI, Hermes, Qwen Code, and Kimi Code. Catalog entries create the same `extends: "acp"` provider config shown below.
Paseo also ships an in-app ACP provider catalog for common agents, including Google Antigravity CLI, Cursor, DeepAgents, DeepSeek TUI, DimCode, Gemini CLI, Hermes, Qwen Code, and Kimi Code. Catalog entries create the same `extends: "acp"` provider config shown below.
### Adding a generic ACP provider

1
package-lock.json generated
View File

@@ -36626,6 +36626,7 @@
"name": "@getpaseo/cli",
"version": "0.1.78",
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@clack/prompts": "^1.0.0",
"@getpaseo/server": "0.1.78",
"chalk": "^5.3.0",

View File

@@ -5,6 +5,8 @@ export const ACP_PROVIDER_ICON_SVGS = {
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">\n <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/>\n <path d="M5.5 10.5L8 4L10.5 10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>\n <line x1="6.25" y1="8.75" x2="9.75" y2="8.75" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>\n</svg>\n',
"amp-acp":
'<svg width="16" height="16" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">\n<path d="M13.9197 13.61L17.3816 26.566L14.242 27.4049L11.2645 16.2643L0.119926 13.2906L0.957817 10.15L13.9197 13.61Z" fill="currentColor"/>\n<path d="M13.7391 16.0892L4.88169 24.9056L2.58872 22.6019L11.4461 13.7865L13.7391 16.0892Z" fill="currentColor"/>\n<path d="M18.9386 8.58315L22.4005 21.5392L19.2609 22.3781L16.2833 11.2374L5.13879 8.26381L5.97668 5.12318L18.9386 8.58315Z" fill="currentColor"/>\n<path d="M23.9803 3.55632L27.4422 16.5124L24.3025 17.3512L21.325 6.21062L10.1805 3.23698L11.0183 0.0963593L23.9803 3.55632Z" fill="currentColor"/>\n</svg>\n',
antigravity:
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">\n <path d="M8 1.5L13.75 14.5H11.45L10.2 11.45H5.75L4.55 14.5H2.25L8 1.5ZM6.5 9.55H9.45L8 5.95L6.5 9.55Z" fill="currentColor"/>\n <path d="M2.25 7.9C2.25 5.1 4.55 2.85 7.4 2.85C10.75 2.85 13.35 5.65 13.35 8.9C13.35 11.4 11.35 13.15 9.1 13.15" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/>\n</svg>\n',
auggie:
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16">\n <path fill="currentColor" d="M9.972 13.193h2.577q.187 0 .277-.09t.091-.294V10.47q0-.324.133-.59.135-.27.36-.424a1 1 0 0 1 .099-.06 1 1 0 0 1-.1-.058 1.1 1.1 0 0 1-.359-.424 1.3 1.3 0 0 1-.133-.59v-2.33q0-.202-.091-.293t-.277-.089H9.972a.38.38 0 0 1-.294-.124.4.4 0 0 1-.108-.281q.002-.176.131-.293a.46.46 0 0 1 .313-.111h2.775c.36 0 .642.097.836.288q.293.29.294.832V8.24q0 .39.152.562.148.168.555.178a.4.4 0 0 1 .27.128.41.41 0 0 1 .104.287.44.44 0 0 1-.1.282.34.34 0 0 1-.277.132c-.266.006-.452.066-.55.177q-.151.174-.152.573v2.318c0 .235-.043.44-.128.607a.85.85 0 0 1-.389.387 1.3 1.3 0 0 1-.534.124V14h-2.854a.45.45 0 0 1-.315-.118.38.38 0 0 1-.129-.286q0-.164.109-.282a.38.38 0 0 1 .293-.123zM1.103 9.108a.4.4 0 0 1 .276-.128q.4-.008.55-.177.15-.173.151-.562V5.923q.002-.543.294-.832c.195-.191.477-.288.836-.288h2.775q.188 0 .313.111a.37.37 0 0 1 .131.293.4.4 0 0 1-.108.281.38.38 0 0 1-.293.123H3.45q-.186 0-.277.09t-.092.292v2.33c0 .213-.044.413-.133.59a1.1 1.1 0 0 1-.359.424 1 1 0 0 1-.1.059 1 1 0 0 1 .1.059q.225.156.359.423t.133.59v2.34q0 .203.092.293.088.09.277.089h2.577a.38.38 0 0 1 .294.123q.108.12.108.281a.38.38 0 0 1-.13.286.45.45 0 0 1-.314.118l-2.775-.003a1.4 1.4 0 0 1-.613-.126.87.87 0 0 1-.388-.387 1.34 1.34 0 0 1-.129-.608v-2.318q0-.4-.151-.572-.151-.168-.55-.177a.35.35 0 0 1-.278-.132.42.42 0 0 1-.102-.28q0-.173.103-.287"/>\n <path fill="currentColor" d="M5.437 10.36a.986.986 0 0 1-.994-.975c0-.538.446-.976.994-.976s.994.438.994.976a.986.986 0 0 1-.994.976M10.562 8.41c.548 0 .994.437.994.975a.986.986 0 0 1-.994.976.986.986 0 0 1-.994-.976c0-.538.446-.976.994-.976M8.74 2c.37 0 .446.15.446.275v.034q0 .034-.009.201-.01.162-.017.549-.018.378-.044 1.06c0 .106-.065.233-.375.233-.311 0-.376-.126-.376-.23l-.07-1.835v-.003c0-.13.078-.284.446-.284M7.256 2c.369 0 .446.15.446.275v.034q0 .034-.01.201-.007.162-.017.549-.017.378-.043 1.06c0 .106-.065.233-.376.233-.31 0-.375-.126-.375-.23l-.07-1.835v-.003C6.81 2.154 6.887 2 7.256 2"/>\n</svg>\n',
autohand:

View File

@@ -31,6 +31,15 @@ const CATALOG_DATA = [
installLink: "https://github.com/tao12345666333/amp-acp",
command: ["amp-acp"],
},
{
id: "antigravity",
title: "Google Antigravity CLI",
description: "Terminal-first surface to interact with Antigravity agents",
version: "1.0.0",
iconId: "antigravity",
installLink: "https://www.antigravity.google/product/antigravity-cli",
command: ["paseo", "acp", "antigravity"],
},
{
id: "auggie",
title: "Auggie CLI",

View File

@@ -36,6 +36,7 @@ describe("ACP provider catalog", () => {
it("uses PATH commands for entries that were binary distributions upstream", () => {
expect(findProvider("amp-acp").command).toEqual(["amp-acp"]);
expect(findProvider("antigravity").command).toEqual(["paseo", "acp", "antigravity"]);
expect(findProvider("cursor").command).toEqual(["cursor-agent", "acp"]);
expect(findProvider("deepseek-tui").command).toEqual(["deepseek", "serve", "--acp"]);
expect(findProvider("goose").command).toEqual(["goose", "acp"]);
@@ -58,6 +59,14 @@ describe("ACP provider catalog", () => {
});
});
it("bundles the Antigravity catalog metadata and icon", () => {
const provider = findProvider("antigravity");
expect(provider.description).toBe("Terminal-first surface to interact with Antigravity agents");
expect(provider.installLink).toBe("https://www.antigravity.google/product/antigravity-cli");
expect(provider.iconSvg).toContain("<svg");
});
it("preserves provider env in the daemon config patch", () => {
const patch = buildAcpProviderConfigPatch(findProvider("auggie"));

View File

@@ -23,6 +23,7 @@
"test:e2e:lifecycle": "npx tsx tests/e2e/agent-lifecycle.test.ts"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.17.1",
"@clack/prompts": "^1.0.0",
"@getpaseo/server": "0.1.78",
"chalk": "^5.3.0",

View File

@@ -1,5 +1,6 @@
import { Command, Option } from "commander";
import { createAgentCommand } from "./commands/agent/index.js";
import { createAcpCommand } from "./commands/acp/index.js";
import { createDaemonCommand } from "./commands/daemon/index.js";
import { createChatCommand } from "./commands/chat/index.js";
import { createLoopCommand } from "./commands/loop/index.js";
@@ -94,6 +95,7 @@ export function createCli(): Command {
);
// Top-level local daemon shortcuts
program.addCommand(createAcpCommand());
program.addCommand(onboardCommand());
program.addCommand(daemonStartCommand());

View File

@@ -0,0 +1,239 @@
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { Readable, Writable } from "node:stream";
import {
AgentSideConnection,
ndJsonStream,
PROTOCOL_VERSION,
type Agent,
type ContentBlock,
type InitializeRequest,
type InitializeResponse,
type LoadSessionRequest,
type LoadSessionResponse,
type NewSessionRequest,
type NewSessionResponse,
type PromptRequest,
type PromptResponse,
type ResumeSessionRequest,
type ResumeSessionResponse,
} from "@agentclientprotocol/sdk";
interface AntigravitySession {
cwd: string;
hasPrompted: boolean;
}
const DEFAULT_MODEL_ID = "default";
const DEFAULT_MODE_ID = "default";
function promptToText(blocks: ContentBlock[]): string {
return blocks
.map((block) => {
if (block.type === "text") {
return block.text;
}
if (block.type === "resource_link") {
return block.uri;
}
return "";
})
.filter((text) => text.length > 0)
.join("\n\n");
}
function runAgyPrompt(input: {
prompt: string;
cwd: string;
continueConversation: boolean;
signal: AbortSignal;
}): Promise<string> {
const args = [
...(input.continueConversation ? ["--continue"] : []),
"--print",
input.prompt,
"--print-timeout",
"5m",
];
return new Promise((resolve, reject) => {
const child = spawn("agy", args, {
cwd: input.cwd,
env: {
...process.env,
AGY_CLI_DISABLE_AUTO_UPDATE: "1",
},
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
const abort = () => {
child.kill("SIGTERM");
};
input.signal.addEventListener("abort", abort, { once: true });
child.stdout?.setEncoding("utf8");
child.stderr?.setEncoding("utf8");
child.stdout?.on("data", (chunk) => {
stdout += chunk;
});
child.stderr?.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", (error) => {
input.signal.removeEventListener("abort", abort);
reject(error);
});
child.on("close", (code, signal) => {
input.signal.removeEventListener("abort", abort);
if (input.signal.aborted) {
resolve("");
return;
}
if (code === 0) {
resolve(stdout.trim());
return;
}
const details = stderr.trim() || stdout.trim() || `agy exited with ${signal ?? code}`;
reject(new Error(details));
});
});
}
class AntigravityACPAgent implements Agent {
private readonly sessions = new Map<string, AntigravitySession>();
private readonly pendingPrompts = new Map<string, AbortController>();
constructor(private readonly connection: AgentSideConnection) {}
async initialize(_params: InitializeRequest): Promise<InitializeResponse> {
return {
protocolVersion: PROTOCOL_VERSION,
agentInfo: {
name: "Paseo Antigravity ACP Adapter",
version: "1.0.0",
},
agentCapabilities: {
loadSession: true,
sessionCapabilities: {
resume: {},
},
},
};
}
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
const sessionId = randomUUID();
this.sessions.set(sessionId, {
cwd: params.cwd,
hasPrompted: false,
});
return this.buildSessionResponse(sessionId);
}
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
this.sessions.set(params.sessionId, {
cwd: params.cwd,
hasPrompted: true,
});
return this.buildSessionState();
}
async unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
this.sessions.set(params.sessionId, {
cwd: params.cwd,
hasPrompted: true,
});
return this.buildSessionState();
}
async prompt(params: PromptRequest): Promise<PromptResponse> {
const session = this.sessions.get(params.sessionId);
if (!session) {
throw new Error(`Unknown Antigravity session: ${params.sessionId}`);
}
const prompt = promptToText(params.prompt);
const abortController = new AbortController();
this.pendingPrompts.set(params.sessionId, abortController);
try {
const text = await runAgyPrompt({
prompt,
cwd: session.cwd,
continueConversation: session.hasPrompted,
signal: abortController.signal,
});
session.hasPrompted = true;
if (abortController.signal.aborted) {
return { stopReason: "cancelled" };
}
if (text.length > 0) {
await this.connection.sessionUpdate({
sessionId: params.sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text },
messageId: randomUUID(),
},
});
}
return { stopReason: "end_turn" };
} finally {
this.pendingPrompts.delete(params.sessionId);
}
}
async authenticate(): Promise<Record<string, never>> {
return {};
}
async cancel(params: { sessionId: string }): Promise<void> {
this.pendingPrompts.get(params.sessionId)?.abort();
}
private buildSessionResponse(sessionId: string): NewSessionResponse {
return {
sessionId,
...this.buildSessionState(),
};
}
private buildSessionState(): Omit<NewSessionResponse, "sessionId"> {
return {
models: {
currentModelId: DEFAULT_MODEL_ID,
availableModels: [
{
modelId: DEFAULT_MODEL_ID,
name: "Configured Antigravity model",
description: "Uses the model currently selected in Antigravity CLI.",
},
],
},
modes: {
currentModeId: DEFAULT_MODE_ID,
availableModes: [
{
id: DEFAULT_MODE_ID,
name: "Default",
description: "Use Antigravity CLI's configured permissions and settings.",
},
],
},
};
}
}
export function runAntigravityAcpAdapter(): void {
const input = Writable.toWeb(process.stdout);
const output = Readable.toWeb(process.stdin);
const stream = ndJsonStream(input, output);
const connection = new AgentSideConnection(
(agentConnection) => new AntigravityACPAgent(agentConnection),
stream,
);
void connection;
}

View File

@@ -0,0 +1,15 @@
import { Command } from "commander";
import { runAntigravityAcpAdapter } from "./antigravity.js";
export function createAcpCommand(): Command {
const command = new Command("acp").description("Run local ACP adapter commands");
command
.command("antigravity")
.description("Run an ACP adapter for the installed agy CLI")
.action(() => {
runAntigravityAcpAdapter();
});
return command;
}