host.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. export * as PluginHost from "./host"
  2. import type { PluginContext as Interface } from "@opencode-ai/plugin/v2/effect"
  3. import { Effect, Schema } from "effect"
  4. import { AgentV2 } from "../agent"
  5. import { AISDK } from "../aisdk"
  6. import { Catalog } from "../catalog"
  7. import { CommandV2 } from "../command"
  8. import { Credential } from "../credential"
  9. import { Integration } from "../integration"
  10. import { ModelV2 } from "../model"
  11. import { PluginV2 } from "../plugin"
  12. import { ProviderV2 } from "../provider"
  13. import { Reference } from "../reference"
  14. import type { DeepMutable } from "../schema"
  15. import { SkillV2 } from "../skill"
  16. const mutable = <T>(value: T) => value as DeepMutable<T>
  17. export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) {
  18. const agents = yield* AgentV2.Service
  19. const aisdk = yield* AISDK.Service
  20. const catalog = yield* Catalog.Service
  21. const commands = yield* CommandV2.Service
  22. const integration = yield* Integration.Service
  23. const reference = yield* Reference.Service
  24. const skill = yield* SkillV2.Service
  25. return {
  26. options: {},
  27. agent: {
  28. reload: agents.reload,
  29. transform: (callback) =>
  30. agents.transform((draft) =>
  31. callback({
  32. list: () => mutable(draft.list()),
  33. get: (id) => mutable(draft.get(AgentV2.ID.make(id))),
  34. default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
  35. update: (id, update) => draft.update(AgentV2.ID.make(id), update),
  36. remove: (id) => draft.remove(AgentV2.ID.make(id)),
  37. }),
  38. ),
  39. },
  40. aisdk: {
  41. sdk: (callback) =>
  42. aisdk.hook.sdk((event) => {
  43. const output = {
  44. model: mutable(event.model),
  45. package: event.package,
  46. options: event.options,
  47. sdk: event.sdk,
  48. }
  49. const result = callback(output)
  50. return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
  51. Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
  52. )
  53. }),
  54. language: (callback) =>
  55. aisdk.hook.language((event) => {
  56. const output = {
  57. model: mutable(event.model),
  58. sdk: event.sdk,
  59. options: event.options,
  60. language: event.language,
  61. }
  62. const result = callback(output)
  63. return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
  64. Effect.tap(() => Effect.sync(() => (event.language = output.language))),
  65. )
  66. }),
  67. },
  68. catalog: {
  69. reload: catalog.reload,
  70. transform: (callback) =>
  71. catalog.transform((draft) =>
  72. callback({
  73. provider: {
  74. list: () => mutable(draft.provider.list()),
  75. get: (id) => mutable(draft.provider.get(ProviderV2.ID.make(id))),
  76. update: (id, update) => draft.provider.update(ProviderV2.ID.make(id), update),
  77. remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
  78. },
  79. model: {
  80. get: (providerID, modelID) =>
  81. mutable(draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))),
  82. update: (providerID, modelID, update) =>
  83. draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), update),
  84. remove: (providerID, modelID) =>
  85. draft.model.remove(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
  86. default: {
  87. get: draft.model.default.get,
  88. set: (providerID, modelID) =>
  89. draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
  90. },
  91. },
  92. }),
  93. ),
  94. },
  95. command: {
  96. reload: commands.reload,
  97. transform: commands.transform,
  98. },
  99. integration: {
  100. reload: integration.reload,
  101. connection: {
  102. active: (id) => integration.connection.active(Integration.ID.make(id)),
  103. resolve: (connection) =>
  104. integration.connection.resolve(
  105. connection.type === "credential" ? { ...connection, id: Credential.ID.make(connection.id) } : connection,
  106. ),
  107. },
  108. transform: (callback) =>
  109. integration.transform((draft) =>
  110. callback({
  111. list: () => mutable(draft.list()),
  112. get: (id) => mutable(draft.get(Integration.ID.make(id))),
  113. update: (id, update) => draft.update(Integration.ID.make(id), update),
  114. remove: (id) => draft.remove(Integration.ID.make(id)),
  115. method: {
  116. list: (id) => mutable(draft.method.list(Integration.ID.make(id))),
  117. update: (input) => {
  118. if ("authorize" in input) {
  119. const methodID = Integration.MethodID.make(input.method.id)
  120. const refresh = input.refresh
  121. draft.method.update({
  122. integrationID: Integration.ID.make(input.integrationID),
  123. method: { ...input.method, id: methodID },
  124. authorize: (inputs) =>
  125. input.authorize(inputs).pipe(
  126. Effect.map((authorization) => {
  127. if (authorization.mode === "auto") {
  128. return {
  129. ...authorization,
  130. callback: authorization.callback.pipe(
  131. Effect.map((credential) =>
  132. Credential.OAuth.make({
  133. ...credential,
  134. methodID: Integration.MethodID.make(credential.methodID),
  135. }),
  136. ),
  137. ),
  138. }
  139. }
  140. return {
  141. ...authorization,
  142. callback: (code: string) =>
  143. authorization.callback(code).pipe(
  144. Effect.map((credential) =>
  145. Credential.OAuth.make({
  146. ...credential,
  147. methodID: Integration.MethodID.make(credential.methodID),
  148. }),
  149. ),
  150. ),
  151. }
  152. }),
  153. ),
  154. ...(refresh
  155. ? {
  156. refresh: (value: Credential.OAuth) =>
  157. refresh(value).pipe(
  158. Effect.map((next) =>
  159. Credential.OAuth.make({
  160. ...next,
  161. methodID: Integration.MethodID.make(next.methodID),
  162. }),
  163. ),
  164. ),
  165. }
  166. : {}),
  167. ...(input.label ? { label: input.label } : {}),
  168. })
  169. return
  170. }
  171. if (input.method.type === "env") {
  172. draft.method.update({
  173. integrationID: Integration.ID.make(input.integrationID),
  174. method: { type: "env", names: input.method.names },
  175. })
  176. return
  177. }
  178. draft.method.update({
  179. integrationID: Integration.ID.make(input.integrationID),
  180. method: { type: "key", label: input.method.label },
  181. })
  182. },
  183. remove: (id, method) =>
  184. draft.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)),
  185. },
  186. }),
  187. ),
  188. },
  189. plugin: {
  190. add: (input) => plugin.add(PluginV2.ID.make(input.id), input.effect),
  191. remove: (id) => plugin.remove(PluginV2.ID.make(id)),
  192. },
  193. reference: {
  194. reload: reference.reload,
  195. transform: (callback) =>
  196. reference.transform((draft) =>
  197. callback({
  198. add: (name, source) => draft.add(name, Schema.decodeUnknownSync(Reference.Source)(source)),
  199. remove: draft.remove,
  200. list: draft.list,
  201. }),
  202. ),
  203. },
  204. skill: {
  205. reload: skill.reload,
  206. transform: (callback) =>
  207. skill.transform((draft) =>
  208. callback({
  209. source: (source) => draft.source(Schema.decodeUnknownSync(SkillV2.Source)(source)),
  210. list: draft.list,
  211. }),
  212. ),
  213. },
  214. } satisfies Interface
  215. })