event.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { expect, test } from "bun:test"
  2. import type { AgentSideConnection } from "@agentclientprotocol/sdk"
  3. import { OpenCode } from "@opencode-ai/client/promise"
  4. import { streamTurn } from "../../src/acp/event"
  5. test("acp prompt resolves after ordered turn updates", async () => {
  6. const encoder = new TextEncoder()
  7. let events: ReadableStreamDefaultController<Uint8Array> | undefined
  8. const updates: Parameters<AgentSideConnection["sessionUpdate"]>[0][] = []
  9. const server = Bun.serve({
  10. port: 0,
  11. async fetch(request) {
  12. const url = new URL(request.url)
  13. if (url.pathname === "/api/event") {
  14. return new Response(
  15. new ReadableStream<Uint8Array>({
  16. start(controller) {
  17. events = controller
  18. send(controller, { id: "evt_connected", type: "server.connected", data: {} })
  19. },
  20. }),
  21. { headers: { "content-type": "text/event-stream" } },
  22. )
  23. }
  24. if (url.pathname === "/api/session/ses_test/prompt") {
  25. const body: unknown = await request.json()
  26. if (!body || typeof body !== "object") {
  27. return new Response(null, { status: 400 })
  28. }
  29. const id = Reflect.get(body, "id")
  30. if (typeof id !== "string") return new Response(null, { status: 400 })
  31. queueMicrotask(() => {
  32. if (!events) return
  33. send(events, {
  34. id: "evt_promoted",
  35. created: 1,
  36. type: "session.input.promoted",
  37. data: { sessionID: "ses_test", inputID: id },
  38. })
  39. send(events, {
  40. id: "evt_text",
  41. created: 2,
  42. type: "session.text.delta",
  43. data: { sessionID: "ses_test", assistantMessageID: "msg_assistant", ordinal: 0, delta: "hello" },
  44. })
  45. send(events, {
  46. id: "evt_step",
  47. created: 3,
  48. type: "session.step.ended",
  49. data: {
  50. sessionID: "ses_test",
  51. assistantMessageID: "msg_assistant",
  52. finish: "stop",
  53. cost: 0,
  54. tokens: { input: 1, output: 1, reasoning: 0, cache: { read: 0, write: 0 } },
  55. },
  56. })
  57. send(events, {
  58. id: "evt_done",
  59. created: 4,
  60. type: "session.execution.succeeded",
  61. data: { sessionID: "ses_test" },
  62. })
  63. })
  64. return Response.json({ data: {} })
  65. }
  66. if (url.pathname === "/api/session/ses_test/message/msg_assistant") {
  67. return Response.json({
  68. data: {
  69. id: "msg_assistant",
  70. type: "assistant",
  71. agent: "build",
  72. model: { providerID: "test", id: "test-model" },
  73. content: [{ type: "text", text: "hello" }],
  74. finish: "stop",
  75. tokens: { input: 1, output: 1, reasoning: 0, cache: { read: 0, write: 0 } },
  76. time: { created: 1, completed: 4 },
  77. },
  78. })
  79. }
  80. return new Response(null, { status: 404 })
  81. },
  82. })
  83. const client = OpenCode.make({ baseUrl: server.url.toString() })
  84. try {
  85. const id = "msg_prompt"
  86. const response = await streamTurn({
  87. client,
  88. connection: {
  89. sessionUpdate: async (update) => {
  90. updates.push(update)
  91. },
  92. requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
  93. },
  94. sessionID: "ses_test",
  95. cwd: "/workspace",
  96. start: { type: "input", id },
  97. writeTextFile: false,
  98. control: { cancelled: false, admission: new AbortController() },
  99. submit: () => client.session.prompt({ sessionID: "ses_test", id, text: "hi" }),
  100. })
  101. expect(updates).toEqual([
  102. {
  103. sessionId: "ses_test",
  104. update: {
  105. sessionUpdate: "agent_message_chunk",
  106. messageId: "msg_assistant",
  107. content: { type: "text", text: "hello" },
  108. },
  109. },
  110. ])
  111. expect(response).toMatchObject({ stopReason: "end_turn", usage: { totalTokens: 2 } })
  112. } finally {
  113. events?.close()
  114. await server.stop(true)
  115. }
  116. function send(controller: ReadableStreamDefaultController<Uint8Array>, event: unknown) {
  117. controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
  118. }
  119. })