index.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import type { Hooks, Plugin as PluginInstance } from "@opencode-ai/plugin"
  2. import { App } from "../app/app"
  3. import { Config } from "../config/config"
  4. import { Bus } from "../bus"
  5. import { Log } from "../util/log"
  6. import { createOpencodeClient } from "@opencode-ai/sdk"
  7. import { Server } from "../server/server"
  8. import { pathOr } from "remeda"
  9. export namespace Plugin {
  10. const log = Log.create({ service: "plugin" })
  11. const state = App.state("plugin", async (app) => {
  12. const client = createOpencodeClient({
  13. baseUrl: "http://localhost:4096",
  14. fetch: async (...args) => Server.app().fetch(...args),
  15. })
  16. const config = await Config.get()
  17. const hooks = []
  18. for (const plugin of config.plugin ?? []) {
  19. log.info("loading plugin", { path: plugin })
  20. const mod = await import(plugin)
  21. for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
  22. const init = await fn({
  23. client,
  24. app,
  25. $: Bun.$,
  26. })
  27. hooks.push(init)
  28. }
  29. }
  30. return {
  31. hooks,
  32. }
  33. })
  34. type Path<T, Prefix extends string = ""> = T extends object
  35. ? {
  36. [K in keyof T]: K extends string
  37. ? T[K] extends Function | undefined
  38. ? `${Prefix}${K}`
  39. : Path<T[K], `${Prefix}${K}.`>
  40. : never
  41. }[keyof T]
  42. : never
  43. export type FunctionFromKey<T, P extends Path<T>> = P extends `${infer K}.${infer R}`
  44. ? K extends keyof T
  45. ? R extends Path<T[K]>
  46. ? FunctionFromKey<T[K], R>
  47. : never
  48. : never
  49. : P extends keyof T
  50. ? T[P]
  51. : never
  52. export async function trigger<
  53. Name extends Path<Required<Hooks>>,
  54. Input = Parameters<FunctionFromKey<Required<Hooks>, Name>>[0],
  55. Output = Parameters<FunctionFromKey<Required<Hooks>, Name>>[1],
  56. >(fn: Name, input: Input, output: Output): Promise<Output> {
  57. if (!fn) return output
  58. const path = fn.split(".")
  59. for (const hook of await state().then((x) => x.hooks)) {
  60. // @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
  61. // give up.
  62. // try-counter: 2
  63. const fn = pathOr(hook, path, undefined)
  64. if (!fn) continue
  65. // @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
  66. // give up.
  67. // try-counter: 2
  68. await fn(input, output)
  69. }
  70. return output
  71. }
  72. export function init() {
  73. Bus.subscribeAll(async (input) => {
  74. const hooks = await state().then((x) => x.hooks)
  75. for (const hook of hooks) {
  76. hook["event"]?.({
  77. event: input,
  78. })
  79. }
  80. })
  81. }
  82. }