promise.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. export * as PluginPromise from "./promise"
  2. import { define } from "@opencode-ai/plugin/v2/effect"
  3. import type { Plugin, PluginContext, Registration } from "@opencode-ai/plugin/v2/promise"
  4. import { Effect, Scope } from "effect"
  5. // The Effect host hands back this registration shape; mirror it structurally so
  6. // we do not have to alias the Effect package's `Registration` against the Promise one.
  7. type HostRegistration = { readonly dispose: Effect.Effect<void> }
  8. /**
  9. * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
  10. * loader (`PluginV2` / `PluginInternal`) can run it unchanged.
  11. *
  12. * Hook registrations created during the async `setup` attach to the plugin's
  13. * scope, so unloading the plugin disposes them. The captured fiber context
  14. * preserves boot-time batching, so Promise-plugin transforms still coalesce
  15. * into one reload per domain.
  16. */
  17. export function fromPromise(plugin: Plugin) {
  18. return define({
  19. id: plugin.id,
  20. effect: (host) =>
  21. Effect.gen(function* () {
  22. const scope = yield* Scope.Scope
  23. const context = yield* Effect.context<Scope.Scope>()
  24. // Run a hook registration on the plugin scope and resolve once it is registered.
  25. const register = (effect: Effect.Effect<HostRegistration, never, Scope.Scope>): Promise<Registration> =>
  26. Effect.runPromiseWith(context)(Scope.provide(scope)(effect)).then((registration) => ({
  27. dispose: () => Effect.runPromiseWith(context)(registration.dispose),
  28. }))
  29. const run = (effect: Effect.Effect<void>) => Effect.runPromiseWith(context)(effect)
  30. const transform =
  31. <Draft>(domain: {
  32. transform: (
  33. callback: (draft: Draft) => Effect.Effect<void> | void,
  34. ) => Effect.Effect<HostRegistration, never, Scope.Scope>
  35. }) =>
  36. (callback: (draft: Draft) => Promise<void> | void) =>
  37. register(domain.transform((draft) => Effect.promise(() => Promise.resolve(callback(draft)))))
  38. const context2: PluginContext = {
  39. options: host.options,
  40. agent: {
  41. transform: transform(host.agent),
  42. reload: () => run(host.agent.reload()),
  43. },
  44. aisdk: {
  45. sdk: (callback) =>
  46. register(host.aisdk.sdk((event) => Effect.promise(() => Promise.resolve(callback(event))))),
  47. language: (callback) =>
  48. register(host.aisdk.language((event) => Effect.promise(() => Promise.resolve(callback(event))))),
  49. },
  50. catalog: {
  51. transform: transform(host.catalog),
  52. reload: () => run(host.catalog.reload()),
  53. },
  54. command: {
  55. transform: transform(host.command),
  56. reload: () => run(host.command.reload()),
  57. },
  58. integration: {
  59. transform: transform(host.integration),
  60. reload: () => run(host.integration.reload()),
  61. connection: {
  62. active: (id) => Effect.runPromiseWith(context)(host.integration.connection.active(id)),
  63. resolve: (connection) => Effect.runPromiseWith(context)(host.integration.connection.resolve(connection)),
  64. },
  65. },
  66. plugin: {
  67. add: (input) => {
  68. const child = fromPromise(input)
  69. return run(host.plugin.add(child))
  70. },
  71. remove: (id) => run(host.plugin.remove(id)),
  72. },
  73. reference: {
  74. transform: transform(host.reference),
  75. reload: () => run(host.reference.reload()),
  76. },
  77. skill: {
  78. transform: transform(host.skill),
  79. reload: () => run(host.skill.reload()),
  80. },
  81. }
  82. yield* Effect.promise(() => Promise.resolve(plugin.setup(context2)))
  83. }),
  84. })
  85. }