recorded-golden.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import type { HttpRecorder } from "@opencode-ai/http-recorder"
  2. import { describe, type TestOptions } from "bun:test"
  3. import { Effect } from "effect"
  4. import type { ModelRef } from "../src"
  5. import { goldenScenarioTags, runGoldenScenario, type GoldenScenarioID } from "./recorded-scenarios"
  6. import { recordedTests } from "./recorded-test"
  7. import { kebab } from "./recorded-utils"
  8. type Transport = "http" | "websocket"
  9. type ScenarioInput =
  10. | GoldenScenarioID
  11. | {
  12. readonly id: GoldenScenarioID
  13. readonly name?: string
  14. readonly cassette?: string
  15. readonly tags?: ReadonlyArray<string>
  16. readonly maxTokens?: number
  17. readonly temperature?: number | false
  18. readonly timeout?: number | TestOptions
  19. }
  20. type TargetInput = {
  21. readonly name: string
  22. readonly model: ModelRef
  23. readonly protocol?: string
  24. readonly requires?: ReadonlyArray<string>
  25. readonly transport?: Transport
  26. readonly prefix?: string
  27. readonly tags?: ReadonlyArray<string>
  28. readonly metadata?: Record<string, unknown>
  29. readonly options?: HttpRecorder.RecordReplayOptions
  30. readonly scenarios: ReadonlyArray<ScenarioInput>
  31. }
  32. const scenarioInput = (input: ScenarioInput) => (typeof input === "string" ? { id: input } : input)
  33. const scenarioTitle = (id: GoldenScenarioID) => {
  34. if (id === "text") return "streams text"
  35. if (id === "tool-call") return "streams tool call"
  36. return "drives a tool loop"
  37. }
  38. const defaultPrefix = (target: TargetInput) => {
  39. if (target.prefix) return target.prefix
  40. const transport = target.transport === "websocket" ? "-websocket" : ""
  41. return `${target.model.provider}-${target.protocol ?? target.model.route}${transport}`
  42. }
  43. const metadata = (target: TargetInput) => ({
  44. provider: target.model.provider,
  45. protocol: target.protocol,
  46. route: target.model.route,
  47. transport: target.transport ?? "http",
  48. model: target.model.id,
  49. ...target.metadata,
  50. })
  51. const tags = (target: TargetInput) => [
  52. ...(target.transport === "websocket" ? ["transport:websocket"] : []),
  53. ...(target.tags ?? []),
  54. ]
  55. const runTarget = (target: TargetInput) => {
  56. const recorded = recordedTests({
  57. prefix: defaultPrefix(target),
  58. provider: target.model.provider,
  59. protocol: target.protocol,
  60. requires: target.requires,
  61. tags: tags(target),
  62. metadata: metadata(target),
  63. options: target.options,
  64. })
  65. describe(`${target.name} recorded`, () => {
  66. target.scenarios.forEach((raw) => {
  67. const input = scenarioInput(raw)
  68. const name = input.name ?? scenarioTitle(input.id)
  69. recorded.effect.with(
  70. name,
  71. {
  72. cassette: input.cassette,
  73. id: `${kebab(target.name)}-${input.id}`,
  74. tags: [...goldenScenarioTags(input.id), ...(input.tags ?? [])],
  75. },
  76. () =>
  77. Effect.gen(function* () {
  78. yield* runGoldenScenario(input.id, {
  79. id: `recorded_${kebab(target.name).replaceAll("-", "_")}_${input.id.replaceAll("-", "_")}`,
  80. model: target.model,
  81. maxTokens: input.maxTokens,
  82. temperature: input.temperature,
  83. })
  84. }),
  85. input.timeout,
  86. )
  87. })
  88. })
  89. }
  90. export const describeRecordedGoldenScenarios = (targets: ReadonlyArray<TargetInput>) => {
  91. targets.forEach(runTarget)
  92. }