provider-helper.ts 3.9 KB

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