provider-helper.ts 4.1 KB

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