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

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