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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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/core/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. api: { id: ModelV2.ID.make("sap-model"), type: "aisdk", package: fixtureProvider },
  49. })
  50. }
  51. describe("SapAICorePlugin", () => {
  52. it.effect("copies serviceKey option into AICORE_SERVICE_KEY but keeps SDK options to deployment metadata", () =>
  53. withEnv(
  54. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  55. () =>
  56. Effect.gen(function* () {
  57. const plugin = yield* PluginV2.Service
  58. const aisdk = yield* AISDK.Service
  59. yield* addPlugin()
  60. const sdk = yield* aisdk.runSDK({
  61. model: model("sap-ai-core"),
  62. package: fixtureProvider,
  63. options: { name: "sap-ai-core", serviceKey: "service-key" },
  64. })
  65. expect(process.env.AICORE_SERVICE_KEY).toBe("service-key")
  66. expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" })
  67. }),
  68. ),
  69. )
  70. it.effect("preserves existing AICORE_SERVICE_KEY over serviceKey option", () =>
  71. withEnv(
  72. {
  73. AICORE_SERVICE_KEY: "env-service-key",
  74. AICORE_DEPLOYMENT_ID: "deployment",
  75. AICORE_RESOURCE_GROUP: "resource-group",
  76. },
  77. () =>
  78. Effect.gen(function* () {
  79. const plugin = yield* PluginV2.Service
  80. const aisdk = yield* AISDK.Service
  81. yield* addPlugin()
  82. const sdk = yield* aisdk.runSDK({
  83. model: model("sap-ai-core"),
  84. package: fixtureProvider,
  85. options: { name: "sap-ai-core", serviceKey: "option-service-key" },
  86. })
  87. expect(process.env.AICORE_SERVICE_KEY).toBe("env-service-key")
  88. expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" })
  89. }),
  90. ),
  91. )
  92. it.effect("omits deployment and resourceGroup SDK options when no service key is available", () =>
  93. withEnv(
  94. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  95. () =>
  96. Effect.gen(function* () {
  97. const plugin = yield* PluginV2.Service
  98. const aisdk = yield* AISDK.Service
  99. yield* addPlugin()
  100. const sdk = yield* aisdk.runSDK({
  101. model: model("sap-ai-core"),
  102. package: fixtureProvider,
  103. options: { name: "sap-ai-core" },
  104. })
  105. expect(process.env.AICORE_SERVICE_KEY).toBeUndefined()
  106. expect(sdk.sdk.options).toEqual({})
  107. }),
  108. ),
  109. )
  110. it.effect("uses the callable SDK for language selection", () =>
  111. Effect.gen(function* () {
  112. const plugin = yield* PluginV2.Service
  113. const aisdk = yield* AISDK.Service
  114. yield* addPlugin()
  115. const sdk = Object.assign((modelID: string) => ({ modelID, provider: "callable" }), {
  116. languageModel() {
  117. throw new Error("SAP AI Core should call the SDK directly")
  118. },
  119. })
  120. const language = yield* aisdk.runLanguage({ model: model("sap-ai-core"), sdk, options: {} })
  121. expect(language.language as unknown).toEqual({ modelID: "sap-model", provider: "callable" })
  122. }),
  123. )
  124. it.effect("ignores non-SAP AI Core providers", () =>
  125. withEnv(
  126. { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
  127. () =>
  128. Effect.gen(function* () {
  129. const plugin = yield* PluginV2.Service
  130. const aisdk = yield* AISDK.Service
  131. yield* addPlugin()
  132. const sdk = yield* aisdk.runSDK({
  133. model: model("openai"),
  134. package: fixtureProvider,
  135. options: { name: "openai", serviceKey: "service-key" },
  136. })
  137. const language = yield* aisdk.runLanguage({
  138. model: model("openai"),
  139. sdk: () => {
  140. throw new Error("SAP AI Core should ignore other providers")
  141. },
  142. options: {},
  143. })
  144. expect(process.env.AICORE_SERVICE_KEY).toBeUndefined()
  145. expect(sdk.sdk).toBeUndefined()
  146. expect(language.language).toBeUndefined()
  147. }),
  148. ),
  149. )
  150. })