provider-github-copilot.test.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { ModelV2 } from "@opencode-ai/core/model"
  5. import { PluginV2 } from "@opencode-ai/core/plugin"
  6. import { GithubCopilotPlugin } from "@opencode-ai/core/plugin/provider/github-copilot"
  7. import { ProviderV2 } from "@opencode-ai/core/provider"
  8. import { fakeSelectorSdk, it, model } from "./provider-helper"
  9. describe("GithubCopilotPlugin", () => {
  10. it.effect("creates the bundled Copilot SDK for the GitHub Copilot package", () =>
  11. Effect.gen(function* () {
  12. const plugin = yield* PluginV2.Service
  13. yield* plugin.add(GithubCopilotPlugin)
  14. const ignored = yield* plugin.trigger(
  15. "aisdk.sdk",
  16. {
  17. model: model("github-copilot", "gpt-5"),
  18. package: "@ai-sdk/openai-compatible",
  19. options: { name: "github-copilot" },
  20. },
  21. {},
  22. )
  23. const result = yield* plugin.trigger(
  24. "aisdk.sdk",
  25. {
  26. model: model("github-copilot", "gpt-5"),
  27. package: "@ai-sdk/github-copilot",
  28. options: { name: "github-copilot" },
  29. },
  30. {},
  31. )
  32. expect(ignored.sdk).toBeUndefined()
  33. expect(result.sdk).toBeDefined()
  34. }),
  35. )
  36. it.effect("selects languageModel when responses and chat are absent", () =>
  37. Effect.gen(function* () {
  38. const plugin = yield* PluginV2.Service
  39. const calls: string[] = []
  40. yield* plugin.add(GithubCopilotPlugin)
  41. yield* plugin.trigger(
  42. "aisdk.language",
  43. {
  44. model: model("github-copilot", "claude-sonnet-4"),
  45. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  46. options: {},
  47. },
  48. {},
  49. )
  50. expect(calls).toEqual(["languageModel:claude-sonnet-4"])
  51. }),
  52. )
  53. it.effect("selects languageModel with the API model ID when responses and chat are absent", () =>
  54. Effect.gen(function* () {
  55. const plugin = yield* PluginV2.Service
  56. const calls: string[] = []
  57. yield* plugin.add(GithubCopilotPlugin)
  58. yield* plugin.trigger(
  59. "aisdk.language",
  60. {
  61. model: model("github-copilot", "alias", { apiID: ModelV2.ID.make("claude-sonnet-4") }),
  62. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  63. options: {},
  64. },
  65. {},
  66. )
  67. expect(calls).toEqual(["languageModel:claude-sonnet-4"])
  68. }),
  69. )
  70. it.effect("uses responses for gpt-5 models except gpt-5-mini", () =>
  71. Effect.gen(function* () {
  72. const plugin = yield* PluginV2.Service
  73. const calls: string[] = []
  74. yield* plugin.add(GithubCopilotPlugin)
  75. yield* plugin.trigger(
  76. "aisdk.language",
  77. { model: model("github-copilot", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} },
  78. {},
  79. )
  80. yield* plugin.trigger(
  81. "aisdk.language",
  82. { model: model("github-copilot", "gpt-5.1-codex"), sdk: fakeSelectorSdk(calls), options: {} },
  83. {},
  84. )
  85. yield* plugin.trigger(
  86. "aisdk.language",
  87. { model: model("github-copilot", "gpt-4o"), sdk: fakeSelectorSdk(calls), options: {} },
  88. {},
  89. )
  90. yield* plugin.trigger(
  91. "aisdk.language",
  92. { model: model("github-copilot", "gpt-5-mini"), sdk: fakeSelectorSdk(calls), options: {} },
  93. {},
  94. )
  95. yield* plugin.trigger(
  96. "aisdk.language",
  97. { model: model("github-copilot", "gpt-5-mini-2025-08-07"), sdk: fakeSelectorSdk(calls), options: {} },
  98. {},
  99. )
  100. expect(calls).toEqual([
  101. "responses:gpt-5",
  102. "responses:gpt-5.1-codex",
  103. "chat:gpt-4o",
  104. "chat:gpt-5-mini",
  105. "chat:gpt-5-mini-2025-08-07",
  106. ])
  107. }),
  108. )
  109. it.effect("uses the API model ID when selecting responses or chat", () =>
  110. Effect.gen(function* () {
  111. const plugin = yield* PluginV2.Service
  112. const calls: string[] = []
  113. yield* plugin.add(GithubCopilotPlugin)
  114. yield* plugin.trigger(
  115. "aisdk.language",
  116. {
  117. model: model("github-copilot", "default", { apiID: ModelV2.ID.make("gpt-5") }),
  118. sdk: fakeSelectorSdk(calls),
  119. options: {},
  120. },
  121. {},
  122. )
  123. yield* plugin.trigger(
  124. "aisdk.language",
  125. {
  126. model: model("github-copilot", "small", { apiID: ModelV2.ID.make("gpt-5-mini") }),
  127. sdk: fakeSelectorSdk(calls),
  128. options: {},
  129. },
  130. {},
  131. )
  132. yield* plugin.trigger(
  133. "aisdk.language",
  134. {
  135. model: model("github-copilot", "sonnet", { apiID: ModelV2.ID.make("claude-sonnet-4") }),
  136. sdk: fakeSelectorSdk(calls),
  137. options: {},
  138. },
  139. {},
  140. )
  141. expect(calls).toEqual(["responses:gpt-5", "chat:gpt-5-mini", "chat:claude-sonnet-4"])
  142. }),
  143. )
  144. it.effect("disables gpt-5-chat-latest before Copilot language selection", () =>
  145. Effect.gen(function* () {
  146. const plugin = yield* PluginV2.Service
  147. const catalog = yield* Catalog.Service
  148. yield* plugin.add(GithubCopilotPlugin)
  149. const transform = yield* catalog.transform()
  150. yield* transform((catalog) => {
  151. catalog.provider.update(ProviderV2.ID.make("github-copilot"), () => {})
  152. catalog.model.update(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
  153. })
  154. expect(
  155. (yield* catalog.model.get(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-chat-latest"))).enabled,
  156. ).toBe(false)
  157. }),
  158. )
  159. it.effect("does not disable gpt-5-chat-latest for non-Copilot providers", () =>
  160. Effect.gen(function* () {
  161. const plugin = yield* PluginV2.Service
  162. const catalog = yield* Catalog.Service
  163. yield* plugin.add(GithubCopilotPlugin)
  164. const transform = yield* catalog.transform()
  165. yield* transform((catalog) => {
  166. catalog.provider.update(ProviderV2.ID.make("custom-copilot"), () => {})
  167. catalog.model.update(ProviderV2.ID.make("custom-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
  168. })
  169. expect(
  170. (yield* catalog.model.get(ProviderV2.ID.make("custom-copilot"), ModelV2.ID.make("gpt-5-chat-latest"))).enabled,
  171. ).toBe(true)
  172. }),
  173. )
  174. it.effect("ignores non-Copilot providers", () =>
  175. Effect.gen(function* () {
  176. const plugin = yield* PluginV2.Service
  177. const calls: string[] = []
  178. yield* plugin.add(GithubCopilotPlugin)
  179. const result = yield* plugin.trigger(
  180. "aisdk.language",
  181. { model: model("openai", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} },
  182. {},
  183. )
  184. expect(calls).toEqual([])
  185. expect(result.language).toBeUndefined()
  186. }),
  187. )
  188. })