provider-sap-ai-core.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 { Npm } from "@opencode-ai/core/npm"
  7. import { SapAICorePlugin } from "@opencode-ai/core/plugin/provider/sap-ai-core"
  8. import { ProviderV2 } from "@opencode-ai/core/provider"
  9. import { testEffect } from "../lib/effect"
  10. import { PluginTestLayer } from "./fixture"
  11. const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href
  12. const it = testEffect(PluginTestLayer)
  13. const npm = Npm.Service.of({
  14. add: () => Effect.succeed({ directory: "", entrypoint: undefined }),
  15. install: () => Effect.void,
  16. which: () => Effect.succeed(undefined),
  17. })
  18. const addPlugin = Effect.fn(function* () {
  19. const plugin = yield* PluginV2.Service
  20. const host = yield* PluginHost.make()
  21. yield* plugin.add({ id: SapAICorePlugin.id, effect: SapAICorePlugin.effect({ ...host, npm }) })
  22. })
  23. function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) {
  24. return Effect.acquireUseRelease(
  25. Effect.sync(() => {
  26. const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
  27. for (const [key, value] of Object.entries(vars)) {
  28. if (value === undefined) delete process.env[key]
  29. else process.env[key] = value
  30. }
  31. return previous
  32. }),
  33. effect,
  34. (previous) =>
  35. Effect.sync(() => {
  36. for (const [key, value] of Object.entries(previous)) {
  37. if (value === undefined) delete process.env[key]
  38. else process.env[key] = value
  39. }
  40. }),
  41. )
  42. }
  43. function model(providerID: string) {
  44. return new ModelV2.Info({
  45. ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")),
  46. api: { id: ModelV2.ID.make("sap-model"), type: "aisdk", package: fixtureProvider },
  47. })
  48. }
  49. describe("SapAICorePlugin", () => {
  50. it.effect("copies serviceKey option into AICORE_SERVICE_KEY but keeps SDK options to deployment metadata", () =>
  51. withEnv(
  52. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  53. () =>
  54. Effect.gen(function* () {
  55. const plugin = yield* PluginV2.Service
  56. yield* addPlugin()
  57. const sdk = yield* plugin.trigger(
  58. "aisdk.sdk",
  59. {
  60. model: model("sap-ai-core"),
  61. package: fixtureProvider,
  62. options: { name: "sap-ai-core", serviceKey: "service-key" },
  63. },
  64. {},
  65. )
  66. expect(process.env.AICORE_SERVICE_KEY).toBe("service-key")
  67. expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" })
  68. }),
  69. ),
  70. )
  71. it.effect("preserves existing AICORE_SERVICE_KEY over serviceKey option", () =>
  72. withEnv(
  73. {
  74. AICORE_SERVICE_KEY: "env-service-key",
  75. AICORE_DEPLOYMENT_ID: "deployment",
  76. AICORE_RESOURCE_GROUP: "resource-group",
  77. },
  78. () =>
  79. Effect.gen(function* () {
  80. const plugin = yield* PluginV2.Service
  81. yield* addPlugin()
  82. const sdk = yield* plugin.trigger(
  83. "aisdk.sdk",
  84. {
  85. model: model("sap-ai-core"),
  86. package: fixtureProvider,
  87. options: { name: "sap-ai-core", serviceKey: "option-service-key" },
  88. },
  89. {},
  90. )
  91. expect(process.env.AICORE_SERVICE_KEY).toBe("env-service-key")
  92. expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" })
  93. }),
  94. ),
  95. )
  96. it.effect("omits deployment and resourceGroup SDK options when no service key is available", () =>
  97. withEnv(
  98. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  99. () =>
  100. Effect.gen(function* () {
  101. const plugin = yield* PluginV2.Service
  102. yield* addPlugin()
  103. const sdk = yield* plugin.trigger(
  104. "aisdk.sdk",
  105. { model: model("sap-ai-core"), package: fixtureProvider, options: { name: "sap-ai-core" } },
  106. {},
  107. )
  108. expect(process.env.AICORE_SERVICE_KEY).toBeUndefined()
  109. expect(sdk.sdk.options).toEqual({})
  110. }),
  111. ),
  112. )
  113. it.effect("uses the callable SDK for language selection", () =>
  114. Effect.gen(function* () {
  115. const plugin = yield* PluginV2.Service
  116. yield* addPlugin()
  117. const sdk = Object.assign((modelID: string) => ({ modelID, provider: "callable" }), {
  118. languageModel() {
  119. throw new Error("SAP AI Core should call the SDK directly")
  120. },
  121. })
  122. const language = yield* plugin.trigger("aisdk.language", { model: model("sap-ai-core"), sdk, options: {} }, {})
  123. expect(language.language as unknown).toEqual({ modelID: "sap-model", provider: "callable" })
  124. }),
  125. )
  126. it.effect("ignores non-SAP AI Core providers", () =>
  127. withEnv(
  128. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  129. () =>
  130. Effect.gen(function* () {
  131. const plugin = yield* PluginV2.Service
  132. yield* addPlugin()
  133. const sdk = yield* plugin.trigger(
  134. "aisdk.sdk",
  135. {
  136. model: model("openai"),
  137. package: fixtureProvider,
  138. options: { name: "openai", serviceKey: "service-key" },
  139. },
  140. {},
  141. )
  142. const language = yield* plugin.trigger(
  143. "aisdk.language",
  144. {
  145. model: model("openai"),
  146. sdk: () => {
  147. throw new Error("SAP AI Core should ignore other providers")
  148. },
  149. options: {},
  150. },
  151. {},
  152. )
  153. expect(process.env.AICORE_SERVICE_KEY).toBeUndefined()
  154. expect(sdk.sdk).toBeUndefined()
  155. expect(language.language).toBeUndefined()
  156. }),
  157. ),
  158. )
  159. })