Add Antigravity CLI to ACP catalog

This commit is contained in:
Mohamed Boudra
2026-05-20 10:57:29 +07:00
parent 555d10f046
commit 6983ca322d
9 changed files with 279 additions and 1 deletions
+1
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",
+2
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());
@@ -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;
}
+15
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;
}