provider.test.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { describe, expect } from "bun:test"
  2. import { Effect, Option, Schema } from "effect"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { Config } from "@opencode-ai/core/config"
  5. import { ConfigProviderPlugin } from "@opencode-ai/core/config/plugin/provider"
  6. import { ModelV2 } from "@opencode-ai/core/model"
  7. import { PluginV2 } from "@opencode-ai/core/plugin"
  8. import { ProviderV2 } from "@opencode-ai/core/provider"
  9. import { it } from "../plugin/provider-helper"
  10. function request(headers: Record<string, string>, variant?: string) {
  11. return {
  12. headers,
  13. variant,
  14. }
  15. }
  16. const decode = Schema.decodeUnknownSync(Config.Info)
  17. describe("ConfigProviderPlugin.Plugin", () => {
  18. it.effect("loads configured providers and applies later model overrides", () =>
  19. Effect.gen(function* () {
  20. const catalog = yield* Catalog.Service
  21. const plugin = yield* PluginV2.Service
  22. const providerID = ProviderV2.ID.make("custom")
  23. const modelID = ModelV2.ID.make("chat")
  24. const config = Config.Service.of({
  25. entries: () =>
  26. Effect.succeed([
  27. new Config.Document({
  28. type: "document",
  29. info: decode({
  30. model: "custom/first",
  31. providers: {
  32. custom: {
  33. name: "Configured",
  34. env: ["CUSTOM_API_KEY"],
  35. api: { type: "native", settings: {} },
  36. request: request({ first: "first", shared: "first" }),
  37. models: {
  38. chat: {
  39. name: "First",
  40. capabilities: { tools: true, input: ["text"], output: ["text"] },
  41. disabled: true,
  42. limit: { context: 100, output: 50 },
  43. cost: { input: 1, output: 2 },
  44. request: request({ first: "first", shared: "first" }, "retained"),
  45. variants: [
  46. {
  47. id: "fast",
  48. headers: { first: "first", shared: "first" },
  49. },
  50. ],
  51. },
  52. },
  53. },
  54. },
  55. }),
  56. }),
  57. new Config.Document({
  58. type: "document",
  59. info: decode({
  60. model: "custom/default",
  61. providers: {
  62. custom: {
  63. api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
  64. request: request({ last: "last", shared: "last" }),
  65. models: {
  66. default: {
  67. name: "Default",
  68. },
  69. chat: {
  70. api: { id: "api-chat" },
  71. name: "Last",
  72. limit: { output: 75 },
  73. request: request({ last: "last", shared: "last" }),
  74. variants: [
  75. {
  76. id: "fast",
  77. headers: { last: "last", shared: "last" },
  78. },
  79. {
  80. id: "slow",
  81. headers: { slow: "slow" },
  82. },
  83. ],
  84. },
  85. },
  86. },
  87. },
  88. }),
  89. }),
  90. new Config.Document({
  91. type: "document",
  92. info: decode({
  93. providers: {
  94. custom: { name: "Renamed" },
  95. },
  96. }),
  97. }),
  98. ]),
  99. })
  100. yield* plugin.add({
  101. ...ConfigProviderPlugin.Plugin,
  102. effect: ConfigProviderPlugin.Plugin.effect.pipe(
  103. Effect.provideService(Config.Service, config),
  104. Effect.provideService(Catalog.Service, catalog),
  105. ),
  106. })
  107. const provider = yield* catalog.provider.get(providerID)
  108. const model = yield* catalog.model.get(providerID, modelID)
  109. expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(ModelV2.ID.make("default"))
  110. expect(provider.name).toBe("Renamed")
  111. expect(provider.env).toEqual(["CUSTOM_API_KEY"])
  112. expect(provider.enabled).toEqual({ via: "custom", data: {} })
  113. expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
  114. expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
  115. expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
  116. expect(model.name).toBe("Last")
  117. expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
  118. expect(model.enabled).toBe(false)
  119. expect(model.limit).toEqual({ context: 100, output: 75 })
  120. expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
  121. expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
  122. expect(model.request.variant).toBe("retained")
  123. expect(model.variants.map((variant) => variant.id)).toEqual([
  124. ModelV2.VariantID.make("fast"),
  125. ModelV2.VariantID.make("slow"),
  126. ])
  127. expect(model.variants[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" })
  128. expect(model.variants[1]?.headers).toEqual({ slow: "slow" })
  129. }),
  130. )
  131. })