generate.test.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { expect } from "bun:test"
  2. import { Model } 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/google"),
  18. })
  19. const runtime = Model.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. install: () => Effect.die("unused"),
  57. which: () => Effect.die("unused"),
  58. })
  59. const aisdk = Layer.mock(AISDK.Service, {
  60. hook: {
  61. sdk: () => Effect.die("unused"),
  62. language: () => Effect.die("unused"),
  63. },
  64. model: () => Effect.succeed(runtime),
  65. })
  66. const client = TestLLM.clientLayer.pipe(Layer.provide(TestLLM.layer({ fallback: TestLLM.text("OK", "generate") })))
  67. const resolver = ModelResolver.layer.pipe(Layer.provide(Layer.mergeAll(catalog, integrations, npm, aisdk)))
  68. const it = testEffect(Generate.layer.pipe(Layer.provide(Layer.merge(resolver, client))))
  69. const resolverIt = testEffect(resolver)
  70. it.effect("loads dynamic AI SDK models", () =>
  71. Effect.gen(function* () {
  72. const generate = yield* Generate.Service
  73. const result = yield* generate.text({
  74. prompt: "Return exactly OK",
  75. model: Ref.make({ providerID: selected.providerID, id: selected.id }),
  76. })
  77. expect(result).toBe("OK")
  78. }),
  79. )
  80. resolverIt.effect("resolves dynamic models with their catalog metadata", () =>
  81. Effect.gen(function* () {
  82. const resolver = yield* ModelResolver.Service
  83. const result = yield* resolver.resolve(Ref.make({ providerID: selected.providerID, id: selected.id }))
  84. expect(result).toEqual({
  85. model: runtime,
  86. ref: Ref.make({ providerID: selected.providerID, id: selected.id }),
  87. capabilities: selected.capabilities,
  88. cost: selected.cost,
  89. })
  90. }),
  91. )