generate.test.ts 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { expect } from "bun:test"
  2. import { LanguageModel } from "@opencode-ai/ai"
  3. import { OpenAIChat } from "@opencode-ai/ai/protocols"
  4. import { TestLLM } from "@opencode-ai/ai/testing"
  5. import { AISDK } from "@opencode-ai/core/aisdk"
  6. import { Catalog } from "@opencode-ai/core/catalog"
  7. import { Generate } from "@opencode-ai/core/generate"
  8. import { Integration } from "@opencode-ai/core/integration"
  9. import { ModelResolver } from "@opencode-ai/core/model-resolver"
  10. import { ID, Info, Ref } from "@opencode-ai/core/model"
  11. import { Provider } from "@opencode-ai/core/provider"
  12. import { Npm } from "@opencode-ai/util/npm"
  13. import { Effect, Layer } from "effect"
  14. import { testEffect } from "./lib/effect"
  15. const selected = Info.make({
  16. ...Info.default(Provider.ID.make("test-provider"), ID.make("gemini")),
  17. package: Provider.aisdk("@ai-sdk/mistral"),
  18. })
  19. const runtime = LanguageModel.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
  20. const catalog = Layer.mock(Catalog.Service, {
  21. provider: {
  22. get: () => Effect.succeed(undefined),
  23. all: () => Effect.die("unused"),
  24. available: () => Effect.die("unused"),
  25. },
  26. model: {
  27. get: () => Effect.succeed(selected),
  28. all: () => Effect.die("unused"),
  29. available: () => Effect.die("unused"),
  30. default: () => Effect.die("unused"),
  31. small: () => Effect.die("unused"),
  32. },
  33. })
  34. const integrations = Layer.mock(Integration.Service, {
  35. connection: {
  36. active: () => Effect.succeed(undefined),
  37. resolve: () => Effect.die("unused"),
  38. key: () => Effect.die("unused"),
  39. update: () => Effect.die("unused"),
  40. remove: () => Effect.die("unused"),
  41. },
  42. oauth: {
  43. connect: () => Effect.die("unused"),
  44. status: () => Effect.die("unused"),
  45. complete: () => Effect.die("unused"),
  46. cancel: () => Effect.die("unused"),
  47. },
  48. command: {
  49. connect: () => Effect.die("unused"),
  50. status: () => Effect.die("unused"),
  51. cancel: () => Effect.die("unused"),
  52. },
  53. })
  54. const npm = Layer.mock(Npm.Service, {
  55. add: () => 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 = TestLLM.clientLayer.pipe(Layer.provide(TestLLM.layer({ fallback: TestLLM.text("OK", "generate") })))
  66. const resolver = ModelResolver.layer.pipe(Layer.provide(Layer.mergeAll(catalog, integrations, npm, aisdk)))
  67. const it = testEffect(Generate.layer.pipe(Layer.provide(Layer.merge(resolver, client))))
  68. const resolverIt = testEffect(resolver)
  69. it.effect("loads dynamic AI SDK models", () =>
  70. Effect.gen(function* () {
  71. const generate = yield* Generate.Service
  72. const result = yield* generate.text({
  73. prompt: "Return exactly OK",
  74. model: Ref.make({ providerID: selected.providerID, id: selected.id }),
  75. })
  76. expect(result).toBe("OK")
  77. }),
  78. )
  79. resolverIt.effect("resolves dynamic models with their catalog metadata", () =>
  80. Effect.gen(function* () {
  81. const resolver = yield* ModelResolver.Service
  82. const result = yield* resolver.resolve(Ref.make({ providerID: selected.providerID, id: selected.id }))
  83. expect(result).toEqual({
  84. model: runtime,
  85. ref: Ref.make({ providerID: selected.providerID, id: selected.id }),
  86. capabilities: selected.capabilities,
  87. cost: selected.cost,
  88. })
  89. }),
  90. )