provider-openai.test.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { Integration } from "@opencode-ai/core/integration"
  5. import { ModelV2 } from "@opencode-ai/core/model"
  6. import { PluginV2 } from "@opencode-ai/core/plugin"
  7. import { OpenAIPlugin } from "@opencode-ai/core/plugin/provider/openai"
  8. import { ProviderV2 } from "@opencode-ai/core/provider"
  9. import { fakeSelectorSdk, it, model, provider, required } from "./provider-helper"
  10. import { host, integrationHost } from "./host"
  11. function add(plugin: PluginV2.Interface, integrations: Integration.Interface) {
  12. return plugin.add({
  13. id: OpenAIPlugin.id,
  14. effect: OpenAIPlugin.effect(host({ integration: integrationHost(integrations) })).pipe(
  15. Effect.provideService(Integration.Service, integrations),
  16. ),
  17. })
  18. }
  19. describe("OpenAIPlugin", () => {
  20. it.effect("registers browser and headless ChatGPT OAuth methods", () =>
  21. Effect.gen(function* () {
  22. const plugin = yield* PluginV2.Service
  23. yield* add(plugin, yield* Integration.Service)
  24. expect((yield* (yield* Integration.Service).get(Integration.ID.make("openai")))?.methods).toEqual([
  25. {
  26. id: Integration.MethodID.make("chatgpt-browser"),
  27. type: "oauth",
  28. label: "ChatGPT Pro/Plus (browser)",
  29. },
  30. {
  31. id: Integration.MethodID.make("chatgpt-headless"),
  32. type: "oauth",
  33. label: "ChatGPT Pro/Plus (headless)",
  34. },
  35. ])
  36. }),
  37. )
  38. it.effect("creates an OpenAI SDK for @ai-sdk/openai using the provider ID as SDK name", () =>
  39. Effect.gen(function* () {
  40. const plugin = yield* PluginV2.Service
  41. yield* add(plugin, yield* Integration.Service)
  42. const result = yield* plugin.trigger(
  43. "aisdk.sdk",
  44. {
  45. model: model("custom-openai", "gpt-5"),
  46. package: "@ai-sdk/openai",
  47. options: { name: "custom-openai", apiKey: "test" },
  48. },
  49. {},
  50. )
  51. expect(result.sdk?.responses("gpt-5").provider).toBe("custom-openai.responses")
  52. }),
  53. )
  54. it.effect("ignores non-OpenAI SDK packages", () =>
  55. Effect.gen(function* () {
  56. const plugin = yield* PluginV2.Service
  57. yield* add(plugin, yield* Integration.Service)
  58. const result = yield* plugin.trigger(
  59. "aisdk.sdk",
  60. { model: model("openai", "gpt-5"), package: "@ai-sdk/openai-compatible", options: { name: "openai" } },
  61. {},
  62. )
  63. expect(result.sdk).toBeUndefined()
  64. }),
  65. )
  66. it.effect("uses the Responses API for language models", () =>
  67. Effect.gen(function* () {
  68. const plugin = yield* PluginV2.Service
  69. const calls: string[] = []
  70. yield* add(plugin, yield* Integration.Service)
  71. const result = yield* plugin.trigger(
  72. "aisdk.language",
  73. {
  74. model: model("openai", "alias", {
  75. api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
  76. }),
  77. sdk: fakeSelectorSdk(calls),
  78. options: {},
  79. },
  80. {},
  81. )
  82. expect(calls).toEqual(["responses:gpt-5"])
  83. expect(result.language).toBeDefined()
  84. }),
  85. )
  86. it.effect("ignores non-OpenAI providers", () =>
  87. Effect.gen(function* () {
  88. const plugin = yield* PluginV2.Service
  89. const calls: string[] = []
  90. yield* add(plugin, yield* Integration.Service)
  91. const result = yield* plugin.trigger(
  92. "aisdk.language",
  93. { model: model("anthropic", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} },
  94. {},
  95. )
  96. expect(calls).toEqual([])
  97. expect(result.language).toBeUndefined()
  98. }),
  99. )
  100. it.effect("disables gpt-5-chat-latest during catalog transforms", () =>
  101. Effect.gen(function* () {
  102. const plugin = yield* PluginV2.Service
  103. const catalog = yield* Catalog.Service
  104. yield* add(plugin, yield* Integration.Service)
  105. yield* catalog.transform((catalog) => {
  106. const item = provider("openai", { api: { type: "aisdk", package: "@ai-sdk/openai" } })
  107. catalog.provider.update(item.id, (draft) => {
  108. draft.api = item.api
  109. })
  110. catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {})
  111. catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
  112. })
  113. expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5"))).enabled).toBe(true)
  114. expect(
  115. required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5-chat-latest"))).enabled,
  116. ).toBe(false)
  117. }),
  118. )
  119. it.effect("does not disable gpt-5-chat-latest for non-OpenAI providers", () =>
  120. Effect.gen(function* () {
  121. const plugin = yield* PluginV2.Service
  122. const catalog = yield* Catalog.Service
  123. yield* add(plugin, yield* Integration.Service)
  124. yield* catalog.transform((catalog) => {
  125. const item = provider("custom-openai")
  126. catalog.provider.update(item.id, () => {})
  127. catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
  128. })
  129. expect(
  130. required(yield* catalog.model.get(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5-chat-latest")))
  131. .enabled,
  132. ).toBe(true)
  133. }),
  134. )
  135. })