trigger.test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { CrossSpawnSpawner } from "@kirincode-ai/core/cross-spawn-spawner"
  4. import { Npm } from "@kirincode-ai/core/npm"
  5. import path from "path"
  6. import { pathToFileURL } from "url"
  7. import { Account } from "../../src/account/account"
  8. import { Auth } from "../../src/auth"
  9. import { RuntimeFlags } from "../../src/effect/runtime-flags"
  10. import { Plugin } from "../../src/plugin/index"
  11. import { TestInstance } from "../fixture/fixture"
  12. import { testEffect } from "../lib/effect"
  13. import { AccountTest } from "../fake/account"
  14. import { AuthTest } from "../fake/auth"
  15. import { NpmTest } from "../fake/npm"
  16. import { ProviderV2 } from "@kirincode-ai/core/provider"
  17. import { ModelV2 } from "@kirincode-ai/core/model"
  18. import { AppNodeBuilder } from "@kirincode-ai/core/effect/app-node-builder"
  19. import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
  20. const it = testEffect(
  21. AppNodeBuilder.build(LayerNode.group([Plugin.node, CrossSpawnSpawner.node]), [
  22. [Auth.node, AuthTest.empty],
  23. [Account.node, AccountTest.empty],
  24. [Npm.node, NpmTest.noop],
  25. [RuntimeFlags.node, RuntimeFlags.layer({ disableDefaultPlugins: true })],
  26. ]),
  27. )
  28. const systemHook = "experimental.chat.system.transform"
  29. function withProject<A, E, R>(source: string, self: Effect.Effect<A, E, R>) {
  30. return Effect.gen(function* () {
  31. const test = yield* TestInstance
  32. const file = path.join(test.directory, "plugin.ts")
  33. yield* Effect.all(
  34. [
  35. Effect.promise(() => Bun.write(file, source)),
  36. Effect.promise(() =>
  37. Bun.write(
  38. path.join(test.directory, "kirincode.json"),
  39. JSON.stringify(
  40. {
  41. $schema: "https://kirincode.ai/config.json",
  42. plugin: [pathToFileURL(file).href],
  43. },
  44. null,
  45. 2,
  46. ),
  47. ),
  48. ),
  49. ],
  50. { discard: true, concurrency: 2 },
  51. )
  52. return yield* self
  53. })
  54. }
  55. const triggerSystemTransform = Effect.fn("PluginTriggerTest.triggerSystemTransform")(function* () {
  56. const plugin = yield* Plugin.Service
  57. const out = { system: [] as string[] }
  58. yield* plugin.trigger(
  59. systemHook,
  60. {
  61. model: {
  62. providerID: ProviderV2.ID.anthropic,
  63. modelID: ModelV2.ID.make("claude-sonnet-4-6"),
  64. },
  65. },
  66. out,
  67. )
  68. return out.system
  69. })
  70. describe("plugin.trigger", () => {
  71. it.instance("runs synchronous hooks without crashing", () =>
  72. withProject(
  73. [
  74. "export default async () => ({",
  75. ` ${JSON.stringify(systemHook)}: (_input, output) => {`,
  76. ' output.system.unshift("sync")',
  77. " },",
  78. "})",
  79. "",
  80. ].join("\n"),
  81. Effect.gen(function* () {
  82. expect(yield* triggerSystemTransform()).toEqual(["sync"])
  83. }),
  84. ),
  85. )
  86. it.instance("awaits asynchronous hooks", () =>
  87. withProject(
  88. [
  89. "export default async () => ({",
  90. ` ${JSON.stringify(systemHook)}: async (_input, output) => {`,
  91. " await Bun.sleep(1)",
  92. ' output.system.unshift("async")',
  93. " },",
  94. "})",
  95. "",
  96. ].join("\n"),
  97. Effect.gen(function* () {
  98. expect(yield* triggerSystemTransform()).toEqual(["async"])
  99. }),
  100. ),
  101. )
  102. })