feat: upgrade ink for performance, and use stdio not http (#8004)

This commit is contained in:
Michael Neale
2026-03-20 12:23:18 +11:00
committed by GitHub
parent 3f8eec3237
commit 565b06b715
13 changed files with 851 additions and 272 deletions
+1 -2
View File
@@ -24,7 +24,7 @@ target/
*.pdb *.pdb
# UI # UI
./ui/desktop/node_modules node_modules
./ui/desktop/out ./ui/desktop/out
# Generated goose DLLs (built at build time, not checked in) # Generated goose DLLs (built at build time, not checked in)
@@ -41,7 +41,6 @@ debug_*.txt
# Docs # Docs
# Dependencies # Dependencies
/node_modules
# Production # Production
/build /build
+1
View File
@@ -114,6 +114,7 @@ pub fn create_router(server: Arc<AcpServer>) -> Router {
Router::new() Router::new()
.route("/health", get(health)) .route("/health", get(health))
.route("/status", get(health))
.route( .route(
"/acp", "/acp",
post(http::handle_post).with_state(http_state.clone()), post(http::handle_post).with_state(http_state.clone()),
@@ -14,6 +14,6 @@
"arm64" "arm64"
], ],
"files": [ "files": [
"bin/goose-acp-server" "bin/goose"
] ]
} }
+1 -1
View File
@@ -14,6 +14,6 @@
"x64" "x64"
], ],
"files": [ "files": [
"bin/goose-acp-server" "bin/goose"
] ]
} }
@@ -14,6 +14,6 @@
"arm64" "arm64"
], ],
"files": [ "files": [
"bin/goose-acp-server" "bin/goose"
] ]
} }
+1 -1
View File
@@ -14,6 +14,6 @@
"x64" "x64"
], ],
"files": [ "files": [
"bin/goose-acp-server" "bin/goose"
] ]
} }
+1 -1
View File
@@ -14,6 +14,6 @@
"x64" "x64"
], ],
"files": [ "files": [
"bin/goose-acp-server.exe" "bin/goose.exe"
] ]
} }
+800 -206
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -47,7 +47,7 @@ NATIVE_PACKAGES=(
for pkg in "${NATIVE_PACKAGES[@]}"; do for pkg in "${NATIVE_PACKAGES[@]}"; do
pkg_dir="${NPM_DIR}/${pkg}" pkg_dir="${NPM_DIR}/${pkg}"
if [ ! -f "${pkg_dir}/bin/goose-acp-server" ] && [ ! -f "${pkg_dir}/bin/goose-acp-server.exe" ]; then if [ ! -f "${pkg_dir}/bin/goose" ] && [ ! -f "${pkg_dir}/bin/goose.exe" ]; then
echo " SKIP ${pkg} (no binary found — run build-native-packages.sh first)" echo " SKIP ${pkg} (no binary found — run build-native-packages.sh first)"
continue continue
fi fi
+6 -3
View File
@@ -25,12 +25,12 @@
"dependencies": { "dependencies": {
"@agentclientprotocol/sdk": "^0.14.1", "@agentclientprotocol/sdk": "^0.14.1",
"@block/goose-acp": "^0.1.0", "@block/goose-acp": "^0.1.0",
"ink": "^5.1.0", "ink": "^6.8.0",
"ink-text-input": "^6.0.0", "ink-text-input": "^6.0.0",
"marked": "^15.0.12", "marked": "^15.0.12",
"marked-terminal": "^7.3.0", "marked-terminal": "^7.3.0",
"meow": "^13.2.0", "meow": "^13.2.0",
"react": "^18.3.1" "react": "^19.2.4"
}, },
"optionalDependencies": { "optionalDependencies": {
"@block/goose-acp-server-darwin-arm64": "0.1.0", "@block/goose-acp-server-darwin-arm64": "0.1.0",
@@ -39,10 +39,13 @@
"@block/goose-acp-server-linux-x64": "0.1.0", "@block/goose-acp-server-linux-x64": "0.1.0",
"@block/goose-acp-server-win32-x64": "0.1.0" "@block/goose-acp-server-win32-x64": "0.1.0"
}, },
"overrides": {
"react": "19.2.4"
},
"devDependencies": { "devDependencies": {
"@types/marked-terminal": "^6.1.1", "@types/marked-terminal": "^6.1.1",
"@types/node": "^25.2.3", "@types/node": "^25.2.3",
"@types/react": "^18.3.0", "@types/react": "^19.2.0",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"tsx": "^4.19.0", "tsx": "^4.19.0",
"typescript": "^5.7.0" "typescript": "^5.7.0"
+5 -5
View File
@@ -29,9 +29,9 @@ build_target() {
local pkg_dir="${NPM_DIR}/goose-acp-server-${platform}" local pkg_dir="${NPM_DIR}/goose-acp-server-${platform}"
local bin_dir="${pkg_dir}/bin" local bin_dir="${pkg_dir}/bin"
echo "==> Building goose-acp-server for ${platform} (${rust_target})" echo "==> Building goose for ${platform} (${rust_target})"
cargo build --release --target "${rust_target}" --bin goose-acp-server cargo build --release --target "${rust_target}" --bin goose
mkdir -p "${bin_dir}" mkdir -p "${bin_dir}"
@@ -40,10 +40,10 @@ build_target() {
ext=".exe" ext=".exe"
fi fi
cp "${REPO_ROOT}/target/${rust_target}/release/goose-acp-server${ext}" "${bin_dir}/goose-acp-server${ext}" cp "${REPO_ROOT}/target/${rust_target}/release/goose${ext}" "${bin_dir}/goose${ext}"
chmod +x "${bin_dir}/goose-acp-server${ext}" chmod +x "${bin_dir}/goose${ext}"
echo " Placed binary at ${bin_dir}/goose-acp-server${ext}" echo " Placed binary at ${bin_dir}/goose${ext}"
} }
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
+1 -2
View File
@@ -35,8 +35,7 @@ let binaryPath;
try { try {
// Resolve the package directory, then point at the binary inside it // Resolve the package directory, then point at the binary inside it
const pkgDir = dirname(require.resolve(`${pkg}/package.json`)); const pkgDir = dirname(require.resolve(`${pkg}/package.json`));
const binName = const binName = process.platform === "win32" ? "goose.exe" : "goose";
process.platform === "win32" ? "goose-acp-server.exe" : "goose-acp-server";
binaryPath = join(pkgDir, "bin", binName); binaryPath = join(pkgDir, "bin", binName);
} catch { } catch {
// The optional dependency wasn't installed (e.g. wrong platform). That's fine. // The optional dependency wasn't installed (e.g. wrong platform). That's fine.
+31 -48
View File
@@ -5,6 +5,7 @@ import type { DOMElement } from "ink";
import TextInput from "ink-text-input"; import TextInput from "ink-text-input";
import meow from "meow"; import meow from "meow";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -15,7 +16,9 @@ import type {
ToolCallContent, ToolCallContent,
ToolCallStatus, ToolCallStatus,
ToolKind, ToolKind,
Stream,
} from "@agentclientprotocol/sdk"; } from "@agentclientprotocol/sdk";
import { ndJsonStream } from "@agentclientprotocol/sdk";
import { GooseClient } from "@block/goose-acp"; import { GooseClient } from "@block/goose-acp";
import { renderMarkdown } from "./markdown.js"; import { renderMarkdown } from "./markdown.js";
import { buildToolCallCardLines, ToolCallCompact, findFeaturedToolCallId } from "./toolcall.js"; import { buildToolCallCardLines, ToolCallCompact, findFeaturedToolCallId } from "./toolcall.js";
@@ -539,10 +542,10 @@ function SplashScreen({
} }
function App({ function App({
serverUrl, serverConnection,
initialPrompt, initialPrompt,
}: { }: {
serverUrl: string; serverConnection: Stream | string;
initialPrompt?: string; initialPrompt?: string;
}) { }) {
const { exit } = useApp(); const { exit } = useApp();
@@ -813,7 +816,7 @@ function App({
}); });
}, },
}), }),
serverUrl, serverConnection,
); );
if (cancelled) return; if (cancelled) return;
@@ -856,7 +859,7 @@ function App({
cancelled = true; cancelled = true;
}; };
}, [ }, [
serverUrl, serverConnection,
initialPrompt, initialPrompt,
sendPrompt, sendPrompt,
appendAgent, appendAgent,
@@ -1106,9 +1109,6 @@ const cli = meow(
}, },
); );
const DEFAULT_PORT = 3284;
const DEFAULT_URL = `http://127.0.0.1:${DEFAULT_PORT}`;
function findServerBinary(): string | null { function findServerBinary(): string | null {
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -1129,56 +1129,39 @@ function findServerBinary(): string | null {
return null; return null;
} }
async function waitForServer(url: string, timeoutMs = 10_000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
try {
const res = await fetch(`${url}/status`);
if (res.ok) return;
} catch {
// server not ready yet
}
await new Promise((r) => setTimeout(r, 200));
}
throw new Error(
`Server did not become ready at ${url} within ${timeoutMs}ms`,
);
}
let serverProcess: ReturnType<typeof spawn> | null = null; let serverProcess: ReturnType<typeof spawn> | null = null;
async function main() { async function main() {
let serverUrl = cli.flags.server; let serverConnection: Stream | string;
if (!serverUrl) { if (cli.flags.server) {
serverConnection = cli.flags.server;
} else {
const binary = findServerBinary(); const binary = findServerBinary();
if (binary) { if (!binary) {
serverProcess = spawn(binary, ["--port", String(DEFAULT_PORT)], { console.error(
stdio: "ignore", "No goose binary found. Use --server <url> or install the native package.",
detached: false, );
}); process.exit(1);
serverProcess.on("error", (err) => {
console.error(`Failed to start goose-acp-server: ${err.message}`);
process.exit(1);
});
try {
await waitForServer(DEFAULT_URL);
} catch (err) {
console.error((err as Error).message);
serverProcess.kill();
process.exit(1);
}
serverUrl = DEFAULT_URL;
} else {
serverUrl = DEFAULT_URL;
} }
serverProcess = spawn(binary, ["acp"], {
stdio: ["pipe", "pipe", "ignore"],
detached: false,
});
serverProcess.on("error", (err) => {
console.error(`Failed to start goose acp: ${err.message}`);
process.exit(1);
});
const output = Writable.toWeb(serverProcess.stdin!) as WritableStream<Uint8Array>;
const input = Readable.toWeb(serverProcess.stdout!) as ReadableStream<Uint8Array>;
serverConnection = ndJsonStream(output, input);
} }
const { waitUntilExit } = render( const { waitUntilExit } = render(
<App serverUrl={serverUrl} initialPrompt={cli.flags.text} />, <App serverConnection={serverConnection} initialPrompt={cli.flags.text} />,
); );
await waitUntilExit(); await waitUntilExit();