question.test.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { describe, expect } from "bun:test"
  2. import { Effect, Fiber, Layer } from "effect"
  3. import { QuestionTool } from "../../src/tool/question"
  4. import { Question } from "../../src/question"
  5. import { SessionID, MessageID } from "../../src/session/schema"
  6. import { Agent } from "../../src/agent/agent"
  7. import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
  8. import { Truncate } from "@/tool/truncate"
  9. import { testEffect } from "../lib/effect"
  10. const ctx = {
  11. sessionID: SessionID.make("ses_test-session"),
  12. messageID: MessageID.make("msg_test-message"),
  13. callID: "test-call",
  14. agent: "test-agent",
  15. abort: AbortSignal.any([]),
  16. messages: [],
  17. metadata: () => Effect.void,
  18. ask: () => Effect.void,
  19. }
  20. const it = testEffect(
  21. Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer),
  22. )
  23. const pending = Effect.fn("QuestionToolTest.pending")(function* (question: Question.Interface) {
  24. for (;;) {
  25. const items = yield* question.list()
  26. const item = items[0]
  27. if (item) return item
  28. yield* Effect.sleep("10 millis")
  29. }
  30. })
  31. describe("tool.question", () => {
  32. it.instance("should successfully execute with valid question parameters", () =>
  33. Effect.gen(function* () {
  34. const question = yield* Question.Service
  35. const toolInfo = yield* QuestionTool
  36. const tool = yield* toolInfo.init()
  37. const questions = [
  38. {
  39. question: "What is your favorite color?",
  40. header: "Color",
  41. options: [
  42. { label: "Red", description: "The color of passion" },
  43. { label: "Blue", description: "The color of sky" },
  44. ],
  45. multiple: false,
  46. },
  47. ]
  48. const fiber = yield* tool.execute({ questions }, ctx).pipe(Effect.forkScoped)
  49. const item = yield* pending(question)
  50. yield* question.reply({ requestID: item.id, answers: [["Red"]] })
  51. const result = yield* Fiber.join(fiber)
  52. expect(result.title).toBe("Asked 1 question")
  53. }),
  54. )
  55. it.instance("should now pass with a header longer than 12 but less than 30 chars", () =>
  56. Effect.gen(function* () {
  57. const question = yield* Question.Service
  58. const toolInfo = yield* QuestionTool
  59. const tool = yield* toolInfo.init()
  60. const questions = [
  61. {
  62. question: "What is your favorite animal?",
  63. header: "This Header is Over 12",
  64. options: [{ label: "Dog", description: "Man's best friend" }],
  65. },
  66. ]
  67. const fiber = yield* tool.execute({ questions }, ctx).pipe(Effect.forkScoped)
  68. const item = yield* pending(question)
  69. yield* question.reply({ requestID: item.id, answers: [["Dog"]] })
  70. const result = yield* Fiber.join(fiber)
  71. expect(result.output).toContain(`"What is your favorite animal?"="Dog"`)
  72. }),
  73. )
  74. // intentionally removed the zod validation due to tool call errors, hoping prompting is gonna be good enough
  75. // test("should throw an Error for header exceeding 30 characters", async () => {
  76. // const tool = await QuestionTool.init()
  77. // const questions = [
  78. // {
  79. // question: "What is your favorite animal?",
  80. // header: "This Header is Definitely More Than Thirty Characters Long",
  81. // options: [{ label: "Dog", description: "Man's best friend" }],
  82. // },
  83. // ]
  84. // try {
  85. // await tool.execute({ questions }, ctx)
  86. // // If it reaches here, the test should fail
  87. // expect(true).toBe(false)
  88. // } catch (e: any) {
  89. // expect(e).toBeInstanceOf(Error)
  90. // expect(e.cause).toBeInstanceOf(z.ZodError)
  91. // }
  92. // })
  93. // test("should throw an Error for label exceeding 30 characters", async () => {
  94. // const tool = await QuestionTool.init()
  95. // const questions = [
  96. // {
  97. // question: "A question with a very long label",
  98. // header: "Long Label",
  99. // options: [
  100. // { label: "This is a very, very, very long label that will exceed the limit", description: "A description" },
  101. // ],
  102. // },
  103. // ]
  104. // try {
  105. // await tool.execute({ questions }, ctx)
  106. // // If it reaches here, the test should fail
  107. // expect(true).toBe(false)
  108. // } catch (e: any) {
  109. // expect(e).toBeInstanceOf(Error)
  110. // expect(e.cause).toBeInstanceOf(z.ZodError)
  111. // }
  112. // })
  113. })