generate.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { expect } from "bun:test"
  2. import { LLMClient, LLMEvent, LLMResponse, Model } from "@opencode-ai/ai"
  3. import { OpenAIChat } from "@opencode-ai/ai/protocols"
  4. import { AISDK } from "@opencode-ai/core/aisdk"
  5. import { Catalog } from "@opencode-ai/core/catalog"
  6. import { Generate } from "@opencode-ai/core/generate"
  7. import { Integration } from "@opencode-ai/core/integration"
  8. import { ModelResolver } from "@opencode-ai/core/model-resolver"
  9. import { ModelV2 } from "@opencode-ai/core/model"
  10. import { ProviderV2 } from "@opencode-ai/core/provider"
  11. import { Npm } from "@opencode-ai/util/npm"
  12. import { Effect, Layer, Stream } from "effect"
  13. import { testEffect } from "./lib/effect"
  14. const selected = ModelV2.Info.make({
  15. ...ModelV2.Info.empty(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("gemini")),
  16. package: ProviderV2.aisdk("@ai-sdk/google"),
  17. })
  18. const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
  19. const catalog = Layer.mock(Catalog.Service, {
  20. provider: {
  21. get: () => Effect.succeed(undefined),
  22. all: () => Effect.die("unused"),
  23. available: () => Effect.die("unused"),
  24. },
  25. model: {
  26. get: () => Effect.succeed(selected),
  27. all: () => Effect.die("unused"),
  28. available: () => Effect.die("unused"),
  29. default: () => Effect.die("unused"),
  30. small: () => Effect.die("unused"),
  31. },
  32. })
  33. const integrations = Layer.mock(Integration.Service, {
  34. connection: {
  35. active: () => Effect.succeed(undefined),
  36. resolve: () => Effect.die("unused"),
  37. key: () => Effect.die("unused"),
  38. update: () => Effect.die("unused"),
  39. remove: () => Effect.die("unused"),
  40. },
  41. oauth: {
  42. connect: () => Effect.die("unused"),
  43. status: () => Effect.die("unused"),
  44. complete: () => Effect.die("unused"),
  45. cancel: () => Effect.die("unused"),
  46. },
  47. command: {
  48. connect: () => Effect.die("unused"),
  49. status: () => Effect.die("unused"),
  50. cancel: () => Effect.die("unused"),
  51. },
  52. })
  53. const npm = Layer.mock(Npm.Service, {
  54. add: () => Effect.die("unused"),
  55. install: () => Effect.die("unused"),
  56. which: () => Effect.die("unused"),
  57. })
  58. const aisdk = Layer.mock(AISDK.Service, {
  59. hook: {
  60. sdk: () => Effect.die("unused"),
  61. language: () => Effect.die("unused"),
  62. },
  63. model: () => Effect.succeed(runtime),
  64. })
  65. const client = Layer.mock(LLMClient.Service)({
  66. prepare: () => Effect.die("unused"),
  67. stream: () => Stream.die("unused"),
  68. generate: () =>
  69. Effect.sync(() => {
  70. const response = LLMResponse.fromEvents([
  71. LLMEvent.textStart({ id: "generate" }),
  72. LLMEvent.textDelta({ id: "generate", text: "OK" }),
  73. LLMEvent.textEnd({ id: "generate" }),
  74. LLMEvent.finish({ reason: "stop" }),
  75. ])
  76. if (!response) throw new Error("Incomplete generate response")
  77. return response
  78. }),
  79. })
  80. const resolver = ModelResolver.layer.pipe(Layer.provide(Layer.mergeAll(catalog, integrations, npm, aisdk)))
  81. const it = testEffect(Generate.layer.pipe(Layer.provide(Layer.merge(resolver, client))))
  82. const resolverIt = testEffect(resolver)
  83. it.effect("loads dynamic AI SDK models", () =>
  84. Effect.gen(function* () {
  85. const generate = yield* Generate.Service
  86. const result = yield* generate.text({
  87. prompt: "Return exactly OK",
  88. model: ModelV2.Ref.make({ providerID: selected.providerID, id: selected.id }),
  89. })
  90. expect(result).toBe("OK")
  91. }),
  92. )
  93. resolverIt.effect("resolves dynamic models with their catalog metadata", () =>
  94. Effect.gen(function* () {
  95. const resolver = yield* ModelResolver.Service
  96. const result = yield* resolver.resolve(ModelV2.Ref.make({ providerID: selected.providerID, id: selected.id }))
  97. expect(result).toEqual({
  98. model: runtime,
  99. ref: ModelV2.Ref.make({ providerID: selected.providerID, id: selected.id }),
  100. capabilities: selected.capabilities,
  101. cost: selected.cost,
  102. })
  103. }),
  104. )