feat: upgrade ink for performance, and use stdio not http (#8004)
This commit is contained in:
+1
-2
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"bin/goose-acp-server"
|
"bin/goose"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"bin/goose-acp-server.exe"
|
"bin/goose.exe"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+800
-206
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user