From 0bc9399e7e2af661c02882a6d674baae0a341e83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:26:38 +0000 Subject: [PATCH 1/2] Initial plan From f8ed43dff84007763f785ca2e05befea9d6aac8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:33:39 +0000 Subject: [PATCH 2/2] Fix prebuilt install: deploy full archive, clean temp dirs, health binary probe Co-authored-by: lmangani <1423657+lmangani@users.noreply.github.com> Agent-Logs-Url: https://github.com/audiohacking/acestep-cpp-api/sessions/f7c28815-bfaa-4451-a983-c4db7b410f3f --- README.md | 17 +++++++++++++---- docs/API.md | 11 ++++++++++- scripts/bundle-acestep.ts | 32 ++++++++++++++++++++++---------- src/index.ts | 30 +++++++++++++++++++++++++++++- src/worker.ts | 11 ++--------- 5 files changed, 76 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 315f3fa..519c790 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,24 @@ ## Bundled acestep.cpp (v0.0.3) -`bun run build` downloads the correct asset from **[acestep.cpp releases v0.0.3](https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3)** for the **current** OS/arch, installs them under `acestep-runtime/bin/`, compiles `dist/acestep-api`, then copies `acestep-runtime` next to the executable: +`bun run build` downloads the correct asset from **[acestep.cpp releases v0.0.3](https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3)** for the **current** OS/arch, installs the **full archive contents** under `acestep-runtime/bin/`, compiles `dist/acestep-api`, then copies `acestep-runtime` next to the executable. + +The prebuilt archives include executables and all shared libraries needed to run them: ```text dist/ - acestep-api # or acestep-api.exe + acestep-api # or acestep-api.exe acestep-runtime/ bin/ - ace-lm - ace-synth + ace-lm # 5Hz LM (text + lyrics → audio codes) + ace-synth # DiT + VAE (audio codes → audio) + ace-server # standalone HTTP server + ace-understand # reverse: audio → metadata + neural-codec # VAE encode/decode utility + mp3-codec # MP3 encoder/decoder utility + quantize # GGUF requantizer + libggml*.so / *.dylib # GGML shared libraries (Linux / macOS) + *.dll # GGML DLLs (Windows) ``` Run the API **from `dist/`** (or anywhere) — the binary resolves siblings via `dirname(execPath)`: diff --git a/docs/API.md b/docs/API.md index 7ee3a84..2308a9a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -552,9 +552,18 @@ curl "http://localhost:8001/v1/audio?path=%2Fabc123.mp3" -o output.mp3 ### 11.2 Response +Runs `ace-synth` without arguments (which prints its usage and exits non-zero) to confirm the binary is present and executable. The `binary` field is `"ok"` when the binary starts successfully, or `"unavailable"` when it cannot be found or run. + ```json { - "data": { "status": "ok", "service": "ACE-Step API", "version": "1.0" }, + "data": { + "status": "ok", + "service": "ACE-Step API", + "version": "1.0", + "binary": "ok", + "binary_path": "/path/to/acestep-runtime/bin/ace-synth", + "binary_hint": "Usage: ace-synth --request ..." + }, "code": 200, "error": null, "timestamp": 1700000000000, diff --git a/scripts/bundle-acestep.ts b/scripts/bundle-acestep.ts index 90779c5..392e2e9 100644 --- a/scripts/bundle-acestep.ts +++ b/scripts/bundle-acestep.ts @@ -1,7 +1,9 @@ #!/usr/bin/env bun /** * Downloads acestep.cpp v0.0.3 release binaries for the current OS/arch and - * installs them under /acestep-runtime/bin (ace-lm, ace-synth). + * installs the full archive contents under /acestep-runtime/bin + * (ace-lm, ace-synth, ace-server, ace-understand, neural-codec, mp3-codec, + * quantize, and all shared libraries). * * @see https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3 * @see https://github.com/audiohacking/acestep.cpp/blob/master/README.md @@ -89,25 +91,35 @@ async function main() { const all = await walkFiles(extractRoot); const wantLm = process.platform === "win32" ? "ace-lm.exe" : "ace-lm"; const wantSynth = process.platform === "win32" ? "ace-synth.exe" : "ace-synth"; - let lm = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantLm); - let synth = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantSynth); + const lm = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantLm); + const synth = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantSynth); if (!lm || !synth) { throw new Error(`Could not find ${wantLm} / ${wantSynth} under ${extractRoot}`); } await rm(outBin, { recursive: true, force: true }); await mkdir(outBin, { recursive: true }); - const outLm = join(outBin, wantLm); - const outSynth = join(outBin, wantSynth); - await copyFile(lm, outLm); - await copyFile(synth, outSynth); + + // Copy every file from the archive root so that shared libraries + // (libggml*.so / *.dylib / *.dll) and helper binaries are all present. + const installed: string[] = []; + for (const srcPath of all) { + const name = srcPath.split(/[/\\]/).pop() ?? ""; + const destPath = join(outBin, name); + await copyFile(srcPath, destPath); + installed.push(destPath); + } if (process.platform !== "win32") { - await chmod(outLm, 0o755); - await chmod(outSynth, 0o755); + // Make all regular files (not static libs) executable so every binary works. + for (const destPath of installed) { + if (!destPath.endsWith(".a")) { + await chmod(destPath, 0o755); + } + } } - console.log(`[bundle-acestep] Installed:\n ${outLm}\n ${outSynth}`); + console.log(`[bundle-acestep] Installed ${installed.length} file(s) to ${outBin}:\n ${installed.map((p) => p.split(/[/\\]/).pop()).join("\n ")}`); } main().catch((e) => { diff --git a/src/index.ts b/src/index.ts index 370883e..f7128d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { mkdir } from "fs/promises"; import { join, resolve } from "path"; +import { existsSync } from "fs"; import { config } from "./config"; import { requireAuth } from "./auth"; import { jsonRes } from "./res"; @@ -12,6 +13,25 @@ import { isPathWithin } from "./paths"; const AUDIO_PATH_PREFIX = "/"; +/** Run ace-synth with no arguments to confirm the binary is present and executable. */ +async function probeAceSynth(): Promise<{ ok: boolean; path: string; hint: string }> { + const binDir = config.acestepBinDir; + const bin = join(binDir, process.platform === "win32" ? "ace-synth.exe" : "ace-synth"); + if (!existsSync(bin)) { + return { ok: false, path: bin, hint: "binary not found" }; + } + try { + const proc = Bun.spawn([bin], { stdout: "pipe", stderr: "pipe" }); + const [stdout, stderr] = await Promise.all([proc.stdout.text(), proc.stderr.text()]); + await proc.exited; + // ace-synth prints usage and exits non-zero when run with no arguments — that is expected. + const out = (stdout + stderr).trim(); + return { ok: true, path: bin, hint: out.slice(0, 300) || "ok" }; + } catch (e) { + return { ok: false, path: bin, hint: e instanceof Error ? e.message : String(e) }; + } +} + function parsePath(pathParam: string): string { const decoded = decodeURIComponent(pathParam); if (decoded.startsWith(AUDIO_PATH_PREFIX)) return decoded; @@ -157,7 +177,15 @@ async function handle(req: Request): Promise { if (path === "/health" && req.method === "GET") { const authErr = requireAuth(req.headers.get("Authorization"), undefined); if (authErr) return authErr; - return jsonRes({ status: "ok", service: "ACE-Step API", version: "1.0" }); + const probe = await probeAceSynth(); + return jsonRes({ + status: "ok", + service: "ACE-Step API", + version: "1.0", + binary: probe.ok ? "ok" : "unavailable", + binary_path: probe.path, + binary_hint: probe.hint, + }); } if (path === "/v1/models" && req.method === "GET") { diff --git a/src/worker.ts b/src/worker.ts index 8b7b53b..4beeba3 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,5 +1,5 @@ import { randomUUID } from "crypto"; -import { mkdir, writeFile, rename, unlink, readdir, readFile } from "fs/promises"; +import { mkdir, writeFile, rename, rm, readdir, readFile } from "fs/promises"; import { join, resolve } from "path"; import { config } from "./config"; import * as store from "./store"; @@ -356,14 +356,7 @@ export async function runPipeline(taskId: string): Promise { store.setTaskFailed(taskId, msg, JSON.stringify([failItem])); } finally { store.recordJobDuration(Date.now() - started); - try { - const entries = await readdir(jobDir).catch(() => []); - for (const e of entries) { - await unlink(join(jobDir, e)).catch(() => {}); - } - } catch { - // ignore - } + await rm(jobDir, { recursive: true, force: true }).catch(() => {}); } }