question.test.ts 4.6 KB

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