provider-openai-compatible.test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { ModelV2 } from "@opencode-ai/core/model"
  4. import { PluginV2 } from "@opencode-ai/core/plugin"
  5. import { PluginHost } from "@opencode-ai/core/plugin/host"
  6. import { OpenAICompatiblePlugin } from "@opencode-ai/core/plugin/provider/openai-compatible"
  7. import { ProviderV2 } from "@opencode-ai/core/provider"
  8. import { testEffect } from "../lib/effect"
  9. import { PluginTestLayer } from "./fixture"
  10. const it = testEffect(PluginTestLayer)
  11. const addPlugin = Effect.fn(function* () {
  12. const plugin = yield* PluginV2.Service
  13. const host = yield* PluginHost.make()
  14. yield* plugin.add({ id: OpenAICompatiblePlugin.id, effect: OpenAICompatiblePlugin.effect(host) })
  15. })
  16. describe("OpenAICompatiblePlugin", () => {
  17. it.effect("preserves explicit includeUsage false and defaults it to true", () =>
  18. Effect.gen(function* () {
  19. const plugin = yield* PluginV2.Service
  20. yield* addPlugin()
  21. const defaulted = yield* plugin.trigger(
  22. "aisdk.sdk",
  23. {
  24. model: new ModelV2.Info({
  25. ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
  26. api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
  27. }),
  28. package: "@ai-sdk/openai-compatible",
  29. options: { name: "custom" },
  30. },
  31. {},
  32. )
  33. const disabled = yield* plugin.trigger(
  34. "aisdk.sdk",
  35. {
  36. model: new ModelV2.Info({
  37. ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
  38. api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
  39. }),
  40. package: "@ai-sdk/openai-compatible",
  41. options: { name: "custom", includeUsage: false },
  42. },
  43. {},
  44. )
  45. expect(defaulted.options.includeUsage).toBe(true)
  46. expect(disabled.options.includeUsage).toBe(false)
  47. }),
  48. )
  49. it.effect("defaults includeUsage for OpenAI-compatible package matches", () =>
  50. Effect.gen(function* () {
  51. const plugin = yield* PluginV2.Service
  52. yield* addPlugin()
  53. const result = yield* plugin.trigger(
  54. "aisdk.sdk",
  55. {
  56. model: new ModelV2.Info({
  57. ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
  58. api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
  59. }),
  60. package: "file:///tmp/@ai-sdk/openai-compatible-provider.js",
  61. options: { name: "custom" },
  62. },
  63. {},
  64. )
  65. expect(result.options.includeUsage).toBe(true)
  66. }),
  67. )
  68. it.effect("uses the provider ID as the OpenAI-compatible provider name", () =>
  69. Effect.gen(function* () {
  70. const plugin = yield* PluginV2.Service
  71. const observed: string[] = []
  72. yield* addPlugin()
  73. yield* plugin.hook("aisdk.sdk", (event) =>
  74. Effect.sync(() => {
  75. observed.push(event.sdk.languageModel("model").provider)
  76. }),
  77. )
  78. yield* plugin.trigger(
  79. "aisdk.sdk",
  80. {
  81. model: new ModelV2.Info({
  82. ...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")),
  83. api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
  84. }),
  85. package: "@ai-sdk/openai-compatible",
  86. options: { name: "custom-provider", baseURL: "https://example.com/v1" },
  87. },
  88. {},
  89. )
  90. expect(observed).toEqual(["custom-provider.chat"])
  91. }),
  92. )
  93. it.effect("does not overwrite an SDK created by an earlier provider-specific plugin", () =>
  94. Effect.gen(function* () {
  95. const plugin = yield* PluginV2.Service
  96. const sentinel = { languageModel: (modelID: string) => ({ modelID }) }
  97. yield* plugin.add({
  98. id: PluginV2.ID.make("sentinel"),
  99. effect: Effect.succeed({
  100. "aisdk.sdk": (evt) =>
  101. Effect.sync(() => {
  102. evt.sdk = sentinel
  103. }),
  104. }),
  105. })
  106. yield* addPlugin()
  107. const result = yield* plugin.trigger(
  108. "aisdk.sdk",
  109. {
  110. model: new ModelV2.Info({
  111. ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")),
  112. api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
  113. }),
  114. package: "@ai-sdk/openai-compatible",
  115. options: { name: "cloudflare-workers-ai" },
  116. },
  117. {},
  118. )
  119. expect(result.sdk).toBe(sentinel)
  120. }),
  121. )
  122. })