service.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { describe, expect, test } from "bun:test"
  2. import type { AgentSideConnection } from "@agentclientprotocol/sdk"
  3. import { OpenCode } from "@opencode-ai/client/promise"
  4. import { ACPService } from "../../src/acp/service"
  5. import { ChildSessionUpdatesCapability } from "../../src/acp/event"
  6. describe("acp service", () => {
  7. test("creates a v2 session, registers mcp, and publishes commands", async () => {
  8. const requests: Array<{ method: string; path: string; body?: unknown }> = []
  9. const updates: Parameters<AgentSideConnection["sessionUpdate"]>[0][] = []
  10. const server = Bun.serve({
  11. port: 0,
  12. async fetch(request) {
  13. const url = new URL(request.url)
  14. requests.push({
  15. method: request.method,
  16. path: url.pathname,
  17. body: request.method === "GET" ? undefined : await request.json().catch(() => undefined),
  18. })
  19. const location = { directory: "/workspace", project: { id: "global", directory: "/workspace" } }
  20. if (url.pathname === "/api/model") return Response.json({ location, data: [model] })
  21. if (url.pathname === "/api/model/default") return Response.json({ location, data: model })
  22. if (url.pathname === "/api/agent") return Response.json({ location, data: [agent] })
  23. if (url.pathname === "/api/command")
  24. return Response.json({ location, data: [{ name: "review", template: "" }] })
  25. if (url.pathname === "/api/skill") return Response.json({ location, data: [skill] })
  26. if (url.pathname === "/api/session" && request.method === "POST") return Response.json({ data: session })
  27. if (url.pathname === "/api/mcp/docs" && request.method === "PUT") return new Response(null, { status: 204 })
  28. return new Response(null, { status: 404 })
  29. },
  30. })
  31. const service = ACPService.make({
  32. client: OpenCode.make({ baseUrl: server.url.toString() }),
  33. connection: {
  34. sessionUpdate: async (update) => {
  35. updates.push(update)
  36. },
  37. requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
  38. },
  39. })
  40. try {
  41. const initialized = await service.initialize({
  42. protocolVersion: 1,
  43. clientCapabilities: { _meta: { [ChildSessionUpdatesCapability]: true } },
  44. clientInfo: { name: "test", version: "1" },
  45. })
  46. const result = await service.newSession({
  47. cwd: "/workspace",
  48. mcpServers: [{ name: "docs", command: "bun", args: ["docs.ts"], env: [{ name: "TOKEN", value: "x" }] }],
  49. })
  50. expect(result.sessionId).toBe("ses_acp")
  51. expect(initialized.agentCapabilities?._meta).toEqual({ [ChildSessionUpdatesCapability]: true })
  52. expect(result.configOptions?.map((option) => option.id)).toEqual(["model", "effort", "mode"])
  53. expect(requests).toContainEqual({
  54. method: "PUT",
  55. path: "/api/mcp/docs",
  56. body: {
  57. config: { type: "local", command: ["bun", "docs.ts"], environment: { TOKEN: "x" } },
  58. },
  59. })
  60. expect(updates.at(-1)).toMatchObject({
  61. sessionId: "ses_acp",
  62. update: {
  63. sessionUpdate: "available_commands_update",
  64. availableCommands: [{ name: "review" }, { name: "verify", description: "Verify work" }],
  65. },
  66. })
  67. } finally {
  68. await server.stop(true)
  69. }
  70. })
  71. })
  72. const model = {
  73. id: "test-model",
  74. modelID: "test-model",
  75. providerID: "test",
  76. name: "Test Model",
  77. capabilities: { tools: true, input: ["text"], output: ["text"] },
  78. variants: [{ id: "default" }, { id: "high" }],
  79. time: { released: 0 },
  80. cost: [],
  81. status: "active" as const,
  82. enabled: true,
  83. limit: { context: 100_000, output: 10_000 },
  84. }
  85. const agent = {
  86. id: "build",
  87. name: "Build",
  88. request: { settings: {}, headers: {}, body: {} },
  89. mode: "primary" as const,
  90. hidden: false,
  91. permissions: [],
  92. }
  93. const skill = {
  94. id: "verify",
  95. name: "verify",
  96. description: "Verify work",
  97. slash: true,
  98. location: "/skills/verify.md",
  99. content: "verify",
  100. }
  101. const session = {
  102. id: "ses_acp",
  103. projectID: "global",
  104. agent: "build",
  105. model: { providerID: "test", id: "test-model", variant: "default" },
  106. cost: 0,
  107. tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
  108. time: { created: 0, updated: 0 },
  109. title: "New session",
  110. location: { directory: "/workspace" },
  111. }