recorded-golden.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import type { HttpRecorder } from "@opencode-ai/http-recorder"
  2. import { describe } from "bun:test"
  3. import { Effect } from "effect"
  4. import type { LanguageModel } from "../src/index.js"
  5. import {
  6. goldenScenarioTags,
  7. goldenScenarioTitle,
  8. runGoldenScenario,
  9. type GoldenScenarioID,
  10. } from "./recorded-scenarios.js"
  11. import { recordedTests } from "./recorded-test.js"
  12. import { kebab } from "./recorded-utils.js"
  13. type Transport = "http" | "websocket"
  14. type ScenarioInput =
  15. | GoldenScenarioID
  16. | {
  17. readonly id: GoldenScenarioID
  18. readonly name?: string
  19. readonly cassette?: string
  20. readonly tags?: ReadonlyArray<string>
  21. readonly maxTokens?: number
  22. readonly temperature?: number | false
  23. readonly timeout?: number
  24. }
  25. type TargetInput = {
  26. readonly name: string
  27. readonly model: LanguageModel
  28. readonly protocol?: string
  29. readonly requires?: ReadonlyArray<string>
  30. readonly transport?: Transport
  31. readonly prefix?: string
  32. readonly tags?: ReadonlyArray<string>
  33. readonly metadata?: HttpRecorder.CassetteMetadata
  34. readonly options?: HttpRecorder.RecorderOptions
  35. readonly scenarios: ReadonlyArray<ScenarioInput>
  36. }
  37. const scenarioInput = (input: ScenarioInput) => (typeof input === "string" ? { id: input } : input)
  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.id}${transport}`
  42. }
  43. const metadata = (target: TargetInput) => ({
  44. provider: target.model.provider,
  45. ...(target.protocol ? { protocol: target.protocol } : {}),
  46. route: target.model.route.id,
  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 ?? goldenScenarioTitle(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. }