provider-helper.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { Npm } from "@opencode-ai/core/npm"
  2. import type { LanguageModelV3 } from "@ai-sdk/provider"
  3. import { expect } from "bun:test"
  4. import { Effect, Layer, Option } from "effect"
  5. import { Catalog } from "@opencode-ai/core/catalog"
  6. import { EventV2 } from "@opencode-ai/core/event"
  7. import { Location } from "@opencode-ai/core/location"
  8. import { ModelV2 } from "@opencode-ai/core/model"
  9. import { PluginV2 } from "@opencode-ai/core/plugin"
  10. import { ProviderV2 } from "@opencode-ai/core/provider"
  11. import { AbsolutePath } from "@opencode-ai/core/schema"
  12. import { location } from "../fixture/location"
  13. import { testEffect } from "../lib/effect"
  14. export const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href
  15. const locationLayer = Layer.succeed(
  16. Location.Service,
  17. Location.Service.of(location({ directory: AbsolutePath.make("test") })),
  18. )
  19. export const npmLayer = Layer.succeed(
  20. Npm.Service,
  21. Npm.Service.of({
  22. add: () => Effect.succeed({ directory: "", entrypoint: Option.none<string>() }),
  23. install: () => Effect.void,
  24. which: () => Effect.succeed(Option.none<string>()),
  25. }),
  26. )
  27. export const catalogLayer = Layer.succeed(
  28. Catalog.Service,
  29. Catalog.Service.of({
  30. transform: () => Effect.die("unexpected catalog.transform"),
  31. provider: {
  32. get: () => Effect.die("unexpected provider.get"),
  33. all: () => Effect.succeed([]),
  34. available: () => Effect.succeed([]),
  35. },
  36. model: {
  37. get: () => Effect.die("unexpected model.get"),
  38. all: () => Effect.succeed([]),
  39. available: () => Effect.succeed([]),
  40. default: () => Effect.succeed(Option.none<ModelV2.Info>()),
  41. small: () => Effect.succeed(Option.none<ModelV2.Info>()),
  42. },
  43. }),
  44. )
  45. export const it = testEffect(
  46. Catalog.locationLayer.pipe(
  47. Layer.provideMerge(EventV2.defaultLayer),
  48. Layer.provideMerge(locationLayer),
  49. Layer.provideMerge(npmLayer),
  50. ),
  51. )
  52. export function provider(providerID: string, options?: Partial<ProviderV2.Info>) {
  53. return new ProviderV2.Info({
  54. ...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)),
  55. endpoint: {
  56. type: "aisdk",
  57. package: "test-provider",
  58. },
  59. ...options,
  60. options: {
  61. headers: {},
  62. body: {},
  63. aisdk: {
  64. provider: {},
  65. request: {},
  66. },
  67. ...options?.options,
  68. },
  69. })
  70. }
  71. export function model(providerID: string, modelID: string, options?: Partial<ModelV2.Info>) {
  72. return new ModelV2.Info({
  73. ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
  74. apiID: ModelV2.ID.make(modelID),
  75. endpoint: {
  76. type: "aisdk",
  77. package: "test-provider",
  78. },
  79. ...options,
  80. options: {
  81. headers: {},
  82. body: {},
  83. aisdk: {
  84. provider: {},
  85. request: {},
  86. },
  87. ...options?.options,
  88. },
  89. })
  90. }
  91. export function withEnv<A, E, R>(vars: Record<string, string | undefined>, fx: () => Effect.Effect<A, E, R>) {
  92. return Effect.acquireUseRelease(
  93. Effect.sync(() => {
  94. const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
  95. for (const [key, value] of Object.entries(vars)) {
  96. if (value === undefined) delete process.env[key]
  97. else process.env[key] = value
  98. }
  99. return previous
  100. }),
  101. () => fx(),
  102. (previous) =>
  103. Effect.sync(() => {
  104. for (const [key, value] of Object.entries(previous)) {
  105. if (value === undefined) delete process.env[key]
  106. else process.env[key] = value
  107. }
  108. }),
  109. )
  110. }
  111. export function fakeSelectorSdk(calls: string[]) {
  112. const make = (method: string) => (id: string) => {
  113. calls.push(`${method}:${id}`)
  114. return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
  115. }
  116. return {
  117. responses: make("responses"),
  118. messages: make("messages"),
  119. chat: make("chat"),
  120. languageModel: make("languageModel"),
  121. }
  122. }
  123. export function expectPluginRegistered(ids: string[], id: string) {
  124. expect(ids).toContain(PluginV2.ID.make(id))
  125. }