system.test.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { describe, expect, test } from "bun:test"
  2. import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
  3. import { Effect, Layer } from "effect"
  4. import type { Agent } from "../../src/agent/agent"
  5. import { NamedError } from "@kirincode-ai/core/util/error"
  6. import { Skill } from "../../src/skill"
  7. import { Permission } from "../../src/permission"
  8. import type { Provider } from "../../src/provider/provider"
  9. import { SystemPrompt } from "../../src/session/system"
  10. import { MCP } from "../../src/mcp"
  11. import { testEffect } from "../lib/effect"
  12. const skills: Skill.Info[] = [
  13. {
  14. name: "zeta-skill",
  15. description: "Zeta skill.",
  16. location: "/tmp/zeta-skill/SKILL.md",
  17. content: "# zeta-skill",
  18. },
  19. {
  20. name: "alpha-skill",
  21. description: "Alpha skill.",
  22. location: "/tmp/alpha-skill/SKILL.md",
  23. content: "# alpha-skill",
  24. },
  25. {
  26. name: "middle-skill",
  27. description: "Middle skill.",
  28. location: "/tmp/middle-skill/SKILL.md",
  29. content: "# middle-skill",
  30. },
  31. {
  32. name: "manual-skill",
  33. location: "/tmp/manual-skill/SKILL.md",
  34. content: "# manual-skill",
  35. },
  36. ]
  37. const build: Agent.Info = {
  38. name: "build",
  39. mode: "primary",
  40. permission: Permission.fromConfig({ "*": "allow" }),
  41. options: {},
  42. }
  43. const it = testEffect(
  44. LayerNode.compile(SystemPrompt.node, [
  45. [
  46. MCP.node,
  47. Layer.mock(MCP.Service, {
  48. instructions: () =>
  49. Effect.succeed([
  50. {
  51. name: "guide-server",
  52. instructions: "Use lookup before mutate.",
  53. tools: [],
  54. },
  55. {
  56. name: "tool-server",
  57. instructions: "Prefer search before update.",
  58. tools: ["tool-server_search", "tool-server_update"],
  59. },
  60. ]),
  61. }),
  62. ],
  63. [
  64. Skill.node,
  65. Layer.succeed(
  66. Skill.Service,
  67. Skill.Service.of({
  68. get: (name) => Effect.succeed(skills.find((skill) => skill.name === name)),
  69. require: (name) => {
  70. const info = skills.find((skill) => skill.name === name)
  71. if (info) return Effect.succeed(info)
  72. return Effect.fail(new Skill.NotFoundError({ name, available: skills.map((skill) => skill.name) }))
  73. },
  74. all: () => Effect.succeed(skills),
  75. dirs: () => Effect.succeed([]),
  76. available: () => Effect.succeed(skills),
  77. }),
  78. ),
  79. ],
  80. ]),
  81. )
  82. describe("session.system", () => {
  83. test("selects the Meta prompt for Muse Spark model IDs", () => {
  84. expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain(
  85. "Meta Muse Spark",
  86. )
  87. })
  88. it.effect("skills output is sorted by name and stable across calls", () =>
  89. Effect.gen(function* () {
  90. const prompt = yield* SystemPrompt.Service
  91. const first = yield* prompt.skills(build)
  92. const second = yield* prompt.skills(build)
  93. const output = first ?? (yield* Effect.fail(new NamedError.Unknown({ message: "missing skills output" })))
  94. expect(first).toBe(second)
  95. const alpha = output.indexOf("<name>alpha-skill</name>")
  96. const middle = output.indexOf("<name>middle-skill</name>")
  97. const zeta = output.indexOf("<name>zeta-skill</name>")
  98. expect(alpha).toBeGreaterThan(-1)
  99. expect(middle).toBeGreaterThan(alpha)
  100. expect(zeta).toBeGreaterThan(middle)
  101. expect(output).not.toContain("manual-skill")
  102. }),
  103. )
  104. it.effect("MCP output includes connected server instructions", () =>
  105. Effect.gen(function* () {
  106. const prompt = yield* SystemPrompt.Service
  107. const output = yield* prompt.mcp(build)
  108. expect(output).toBe(
  109. [
  110. "<mcp_instructions>",
  111. ' <server name="guide-server">',
  112. " Use lookup before mutate.",
  113. " </server>",
  114. ' <server name="tool-server">',
  115. " Prefer search before update.",
  116. " </server>",
  117. "</mcp_instructions>",
  118. ].join("\n"),
  119. )
  120. }),
  121. )
  122. it.effect("MCP output omits servers when all advertised tools are denied", () =>
  123. Effect.gen(function* () {
  124. const prompt = yield* SystemPrompt.Service
  125. const output = yield* prompt.mcp(build, Permission.fromConfig({ "tool-server_*": "deny" }))
  126. expect(output).toBe(
  127. [
  128. "<mcp_instructions>",
  129. ' <server name="guide-server">',
  130. " Use lookup before mutate.",
  131. " </server>",
  132. "</mcp_instructions>",
  133. ].join("\n"),
  134. )
  135. }),
  136. )
  137. })