host.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
  2. import { AgentV2 } from "@opencode-ai/core/agent"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { Integration } from "@opencode-ai/core/integration"
  5. import { ModelV2 } from "@opencode-ai/core/model"
  6. import { ProviderV2 } from "@opencode-ai/core/provider"
  7. import type { IntegrationEnvMethod, IntegrationKeyMethod, IntegrationOAuthMethod } from "@opencode-ai/sdk/v2/types"
  8. import { Effect } from "effect"
  9. type Overrides = Partial<Omit<PluginContext, "options">>
  10. export function host(overrides: Overrides = {}): PluginContext {
  11. return {
  12. options: {},
  13. agent: overrides.agent ?? {
  14. transform: () => Effect.die("unused agent.transform"),
  15. reload: () => Effect.die("unused agent.reload"),
  16. },
  17. aisdk: overrides.aisdk ?? {
  18. sdk: () => Effect.die("unused aisdk.sdk"),
  19. language: () => Effect.die("unused aisdk.language"),
  20. },
  21. catalog: overrides.catalog ?? {
  22. transform: () => Effect.die("unused catalog.transform"),
  23. reload: () => Effect.die("unused catalog.reload"),
  24. },
  25. command: overrides.command ?? {
  26. transform: () => Effect.die("unused command.transform"),
  27. reload: () => Effect.die("unused command.reload"),
  28. },
  29. integration: overrides.integration ?? {
  30. transform: () => Effect.die("unused integration.transform"),
  31. reload: () => Effect.die("unused integration.reload"),
  32. },
  33. plugin: overrides.plugin ?? {
  34. add: () => Effect.die("unused plugin.add"),
  35. remove: () => Effect.die("unused plugin.remove"),
  36. },
  37. reference: overrides.reference ?? {
  38. transform: () => Effect.die("unused reference.transform"),
  39. reload: () => Effect.die("unused reference.reload"),
  40. },
  41. skill: overrides.skill ?? {
  42. transform: () => Effect.die("unused skill.transform"),
  43. reload: () => Effect.die("unused skill.reload"),
  44. },
  45. }
  46. }
  47. export function agentHost(agent: AgentV2.Interface): PluginContext["agent"] {
  48. return {
  49. reload: agent.reload,
  50. transform: (callback) =>
  51. agent.transform((draft) =>
  52. callback({
  53. list: () => draft.list().map(agentInfo),
  54. get: (id) => {
  55. const value = draft.get(AgentV2.ID.make(id))
  56. return value && agentInfo(value)
  57. },
  58. default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
  59. update: (id, update) =>
  60. draft.update(AgentV2.ID.make(id), (value) => {
  61. const current = agentInfo(value)
  62. update(current)
  63. Object.assign(value, current, { id: AgentV2.ID.make(current.id) })
  64. }),
  65. remove: (id) => draft.remove(AgentV2.ID.make(id)),
  66. }),
  67. ),
  68. }
  69. }
  70. export function catalogHost(catalog: Catalog.Interface): PluginContext["catalog"] {
  71. return {
  72. reload: catalog.reload,
  73. transform: (callback) =>
  74. catalog.transform((draft) =>
  75. callback({
  76. provider: {
  77. list: () =>
  78. draft.provider.list().map((value) => ({
  79. provider: providerInfo(value.provider),
  80. models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
  81. })),
  82. get: (id) => {
  83. const value = draft.provider.get(ProviderV2.ID.make(id))
  84. return (
  85. value && {
  86. provider: providerInfo(value.provider),
  87. models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
  88. }
  89. )
  90. },
  91. update: (id, update) =>
  92. draft.provider.update(ProviderV2.ID.make(id), (value) => {
  93. const current = providerInfo(value)
  94. update(current)
  95. Object.assign(value, current, { id: ProviderV2.ID.make(current.id) })
  96. }),
  97. remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
  98. },
  99. model: {
  100. get: (providerID, modelID) => {
  101. const value = draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))
  102. return value && modelInfo(value)
  103. },
  104. update: (providerID, modelID, update) =>
  105. draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), (value) => {
  106. const current = modelInfo(value)
  107. update(current)
  108. Object.assign(value, current, {
  109. id: ModelV2.ID.make(current.id),
  110. providerID: ProviderV2.ID.make(current.providerID),
  111. family: current.family === undefined ? undefined : ModelV2.Family.make(current.family),
  112. variants: current.variants.map((variant) => ({
  113. ...variant,
  114. id: ModelV2.VariantID.make(variant.id),
  115. })),
  116. })
  117. }),
  118. remove: (providerID, modelID) =>
  119. draft.model.remove(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
  120. default: {
  121. get: () => {
  122. const value = draft.model.default.get()
  123. return value && { providerID: value.providerID, modelID: value.modelID }
  124. },
  125. set: (providerID, modelID) =>
  126. draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
  127. },
  128. },
  129. }),
  130. ),
  131. }
  132. }
  133. export function integrationHost(integration: Integration.Interface): PluginContext["integration"] {
  134. return {
  135. reload: integration.reload,
  136. transform: (callback) =>
  137. integration.transform((draft) =>
  138. callback({
  139. list: () => draft.list().map((value) => ({ id: value.id, name: value.name })),
  140. get: (id) => {
  141. const value = draft.get(Integration.ID.make(id))
  142. return value && { id: value.id, name: value.name }
  143. },
  144. update: (id, update) => draft.update(Integration.ID.make(id), update),
  145. remove: (id) => draft.remove(Integration.ID.make(id)),
  146. method: {
  147. list: (id) => draft.method.list(Integration.ID.make(id)).map(method),
  148. update: (input) =>
  149. input.method.type === "env"
  150. ? draft.method.update({
  151. integrationID: Integration.ID.make(input.integrationID),
  152. method: { ...input.method, names: [...input.method.names] },
  153. })
  154. : draft.method.update({
  155. integrationID: Integration.ID.make(input.integrationID),
  156. method: input.method,
  157. }),
  158. remove: (id, item) => draft.method.remove(Integration.ID.make(id), internalMethod(item)),
  159. },
  160. }),
  161. ),
  162. }
  163. }
  164. function method(value: Integration.Method) {
  165. if (value.type === "env") return { type: value.type, names: [...value.names] }
  166. if (value.type === "key") return { type: value.type, label: value.label }
  167. return {
  168. type: value.type,
  169. id: value.id,
  170. label: value.label,
  171. prompts: value.prompts?.map((prompt) => {
  172. if (prompt.type === "text") return { ...prompt }
  173. return { ...prompt, options: prompt.options.map((option) => ({ ...option })) }
  174. }),
  175. }
  176. }
  177. function internalMethod(
  178. value: IntegrationOAuthMethod | IntegrationKeyMethod | IntegrationEnvMethod,
  179. ): Integration.Method {
  180. if (value.type === "env") return value
  181. if (value.type === "key") return value
  182. return {
  183. ...value,
  184. id: Integration.MethodID.make(value.id),
  185. }
  186. }
  187. function agentInfo(value: AgentV2.Info) {
  188. return {
  189. ...value,
  190. model: value.model && { ...value.model },
  191. request: { headers: { ...value.request.headers }, body: { ...value.request.body } },
  192. permissions: value.permissions.map((permission) => ({ ...permission })),
  193. }
  194. }
  195. function providerInfo(value: ProviderV2.MutableInfo) {
  196. return {
  197. ...value,
  198. api: { ...value.api, settings: value.api.settings && { ...value.api.settings } },
  199. request: { headers: { ...value.request.headers }, body: { ...value.request.body } },
  200. }
  201. }
  202. function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
  203. return {
  204. ...value,
  205. api: { ...value.api, settings: value.api.settings && { ...value.api.settings } },
  206. capabilities: {
  207. ...value.capabilities,
  208. input: [...value.capabilities.input],
  209. output: [...value.capabilities.output],
  210. },
  211. request: {
  212. ...value.request,
  213. headers: { ...value.request.headers },
  214. body: { ...value.request.body },
  215. generation: value.request.generation && {
  216. ...value.request.generation,
  217. stop: value.request.generation.stop && [...value.request.generation.stop],
  218. },
  219. options: value.request.options && { ...value.request.options },
  220. },
  221. variants: value.variants.map((variant) => ({
  222. ...variant,
  223. headers: { ...variant.headers },
  224. body: { ...variant.body },
  225. generation: variant.generation && {
  226. ...variant.generation,
  227. stop: variant.generation.stop && [...variant.generation.stop],
  228. },
  229. options: variant.options && { ...variant.options },
  230. })),
  231. time: { ...value.time },
  232. cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),
  233. limit: { ...value.limit },
  234. }
  235. }