trigger.test.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import { afterAll, afterEach, describe, expect, test } from "bun:test"
  2. import { Effect } from "effect"
  3. import path from "path"
  4. import { pathToFileURL } from "url"
  5. import { tmpdir } from "../fixture/fixture"
  6. const disableDefault = process.env.KILO_DISABLE_DEFAULT_PLUGINS
  7. process.env.KILO_DISABLE_DEFAULT_PLUGINS = "1"
  8. const { Plugin } = await import("../../src/plugin/index")
  9. const { Instance } = await import("../../src/project/instance")
  10. afterEach(async () => {
  11. await Instance.disposeAll()
  12. })
  13. afterAll(() => {
  14. if (disableDefault === undefined) {
  15. delete process.env.KILO_DISABLE_DEFAULT_PLUGINS
  16. return
  17. }
  18. process.env.KILO_DISABLE_DEFAULT_PLUGINS = disableDefault
  19. })
  20. async function project(source: string) {
  21. return tmpdir({
  22. init: async (dir) => {
  23. const file = path.join(dir, "plugin.ts")
  24. await Bun.write(file, source)
  25. await Bun.write(
  26. path.join(dir, "opencode.json"),
  27. JSON.stringify(
  28. {
  29. $schema: "https://opencode.ai/config.json",
  30. plugin: [pathToFileURL(file).href],
  31. },
  32. null,
  33. 2,
  34. ),
  35. )
  36. },
  37. })
  38. }
  39. describe("plugin.trigger", () => {
  40. test("runs synchronous hooks without crashing", async () => {
  41. await using tmp = await project(
  42. [
  43. "export default async () => ({",
  44. ' "experimental.chat.system.transform": (_input, output) => {',
  45. ' output.system.unshift("sync")',
  46. " },",
  47. "})",
  48. "",
  49. ].join("\n"),
  50. )
  51. const out = await Instance.provide({
  52. directory: tmp.path,
  53. fn: async () =>
  54. Effect.gen(function* () {
  55. const plugin = yield* Plugin.Service
  56. const out = { system: [] as string[] }
  57. yield* plugin.trigger(
  58. "experimental.chat.system.transform",
  59. {
  60. model: {
  61. providerID: "anthropic",
  62. modelID: "claude-sonnet-4-6",
  63. } as any,
  64. },
  65. out,
  66. )
  67. return out
  68. }).pipe(Effect.provide(Plugin.defaultLayer), Effect.runPromise),
  69. })
  70. expect(out.system).toEqual(["sync"])
  71. })
  72. test("awaits asynchronous hooks", async () => {
  73. await using tmp = await project(
  74. [
  75. "export default async () => ({",
  76. ' "experimental.chat.system.transform": async (_input, output) => {',
  77. " await Bun.sleep(1)",
  78. ' output.system.unshift("async")',
  79. " },",
  80. "})",
  81. "",
  82. ].join("\n"),
  83. )
  84. const out = await Instance.provide({
  85. directory: tmp.path,
  86. fn: async () =>
  87. Effect.gen(function* () {
  88. const plugin = yield* Plugin.Service
  89. const out = { system: [] as string[] }
  90. yield* plugin.trigger(
  91. "experimental.chat.system.transform",
  92. {
  93. model: {
  94. providerID: "anthropic",
  95. modelID: "claude-sonnet-4-6",
  96. } as any,
  97. },
  98. out,
  99. )
  100. return out
  101. }).pipe(Effect.provide(Plugin.defaultLayer), Effect.runPromise),
  102. })
  103. expect(out.system).toEqual(["async"])
  104. })
  105. })