acp.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { Log } from "@/util/log"
  2. import { bootstrap } from "../bootstrap"
  3. import { cmd } from "./cmd"
  4. import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
  5. import { ACP } from "@/acp/agent"
  6. import { Server } from "@/server/server"
  7. import { createOpencodeClient } from "@opencode-ai/sdk/v2"
  8. const log = Log.create({ service: "acp-command" })
  9. process.on("unhandledRejection", (reason, promise) => {
  10. log.error("Unhandled rejection", {
  11. promise,
  12. reason,
  13. })
  14. })
  15. export const AcpCommand = cmd({
  16. command: "acp",
  17. describe: "start ACP (Agent Client Protocol) server",
  18. builder: (yargs) => {
  19. return yargs
  20. .option("cwd", {
  21. describe: "working directory",
  22. type: "string",
  23. default: process.cwd(),
  24. })
  25. .option("port", {
  26. type: "number",
  27. describe: "port to listen on",
  28. default: 0,
  29. })
  30. .option("hostname", {
  31. type: "string",
  32. describe: "hostname to listen on",
  33. default: "127.0.0.1",
  34. })
  35. },
  36. handler: async (args) => {
  37. await bootstrap(process.cwd(), async () => {
  38. const server = Server.listen({
  39. port: args.port,
  40. hostname: args.hostname,
  41. })
  42. const sdk = createOpencodeClient({
  43. baseUrl: `http://${server.hostname}:${server.port}`,
  44. })
  45. const input = new WritableStream<Uint8Array>({
  46. write(chunk) {
  47. return new Promise<void>((resolve, reject) => {
  48. process.stdout.write(chunk, (err) => {
  49. if (err) {
  50. reject(err)
  51. } else {
  52. resolve()
  53. }
  54. })
  55. })
  56. },
  57. })
  58. const output = new ReadableStream<Uint8Array>({
  59. start(controller) {
  60. process.stdin.on("data", (chunk: Buffer) => {
  61. controller.enqueue(new Uint8Array(chunk))
  62. })
  63. process.stdin.on("end", () => controller.close())
  64. process.stdin.on("error", (err) => controller.error(err))
  65. },
  66. })
  67. const stream = ndJsonStream(input, output)
  68. const agent = await ACP.init({ sdk })
  69. new AgentSideConnection((conn) => {
  70. return agent.create(conn, { sdk })
  71. }, stream)
  72. log.info("setup connection")
  73. process.stdin.resume()
  74. await new Promise((resolve, reject) => {
  75. process.stdin.on("end", resolve)
  76. process.stdin.on("error", reject)
  77. })
  78. })
  79. },
  80. })