mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Compare commits
1 Commits
76e336a1be
...
add-antigr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6983ca322d |
@@ -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
1
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
239
packages/cli/src/commands/acp/antigravity.ts
Normal file
239
packages/cli/src/commands/acp/antigravity.ts
Normal 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;
|
||||
}
|
||||
15
packages/cli/src/commands/acp/index.ts
Normal file
15
packages/cli/src/commands/acp/index.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user