instructions.test.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { describe, expect } from "bun:test"
  2. import { CodeMode } from "@opencode-ai/core/codemode"
  3. import { CodeModeCatalog } from "@opencode-ai/core/codemode/catalog"
  4. import { CodeModeInstructions } from "@opencode-ai/core/codemode/instructions"
  5. import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
  6. import { Tool } from "@opencode-ai/core/tool/tool"
  7. import { Effect, Schema } from "effect"
  8. import { it } from "../lib/effect"
  9. import { readInitial, readUpdate } from "../lib/instructions"
  10. const echo: CodeModeCatalog.Entry = {
  11. path: "notes.echo",
  12. description: "Echo text",
  13. signature: "tools.notes.echo(input: {\n text: string,\n}): Promise<string>",
  14. }
  15. const lookup: CodeModeCatalog.Entry = {
  16. path: "orders.lookup",
  17. description: "Look up an order",
  18. signature: "tools.orders.lookup(input: {\n id: string,\n}): Promise<unknown>",
  19. }
  20. describe("CodeModeInstructions", () => {
  21. it.effect("instructs the model not to call execute while the catalog is empty", () =>
  22. Effect.gen(function* () {
  23. const initialized = yield* readInitial(CodeModeInstructions.make([]))
  24. expect(initialized.text).toBe(
  25. "No Code Mode tools are currently available. Later Code Mode catalog updates may add or remove tools. Do not call `execute` unless there is at least one available Code Mode tool.",
  26. )
  27. const added = yield* readUpdate(CodeModeInstructions.make([echo]), initialized)
  28. expect(added.text).toContain("New tools are available in addition to those previously listed:")
  29. expect(added.text).toContain(echo.signature)
  30. expect(yield* readUpdate(CodeModeInstructions.make([]), { values: added.values })).toMatchObject({
  31. text:
  32. "The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.\n\n" +
  33. "No Code Mode tools are currently available. Later Code Mode catalog updates may add or remove tools. Do not call `execute` unless there is at least one available Code Mode tool.",
  34. })
  35. }),
  36. )
  37. it.effect("renders the initial catalog, semantic deltas, and removal", () =>
  38. Effect.gen(function* () {
  39. const initialized = yield* readInitial(CodeModeInstructions.make([echo]))
  40. expect(initialized.text).toContain("## Available tools")
  41. expect(initialized.text).not.toContain("## Search")
  42. expect(initialized.text).toContain(` - ${echo.signature} // Echo text`)
  43. const added = yield* readUpdate(CodeModeInstructions.make([echo, lookup]), initialized)
  44. expect(added.text).toContain("The Code Mode tool catalog has changed.")
  45. expect(added.text).toContain("New tools are available in addition to those previously listed:")
  46. expect(added.text).toContain(` - ${lookup.signature} // Look up an order`)
  47. expect(added.text).not.toContain("## Available tools")
  48. const removed = yield* readUpdate(CodeModeInstructions.make([echo]), { values: added.values })
  49. expect(removed.text).toBe(
  50. "The Code Mode tool catalog has changed.\n\n" +
  51. "The following tools are no longer available and must not be called: tools.orders.lookup.",
  52. )
  53. expect(yield* readUpdate(CodeModeInstructions.make(), initialized)).toMatchObject({
  54. text: "Code Mode tools are no longer available. Do not use any previously listed Code Mode tools.",
  55. })
  56. }),
  57. )
  58. it.effect("stores a canonical sorted snapshot so registration order does not churn history", () => {
  59. const alpha = Tool.make({
  60. description: "Alpha tool",
  61. input: Schema.Struct({}),
  62. output: Schema.String,
  63. execute: () => Effect.succeed({ output: "alpha" }),
  64. })
  65. const zeta = Tool.make({
  66. description: "Zeta tool",
  67. input: Schema.Struct({}),
  68. output: Schema.String,
  69. execute: () => Effect.succeed({ output: "zeta" }),
  70. })
  71. const layer = AppNodeBuilder.build(CodeMode.node)
  72. return Effect.gen(function* () {
  73. const codeMode = yield* CodeMode.Service
  74. const initialized = yield* Effect.scoped(
  75. Effect.gen(function* () {
  76. yield* codeMode.register(Tool.registrationEntries({ zeta, alpha }, { namespace: "tools" }))
  77. return yield* readInitial(CodeModeInstructions.make((yield* codeMode.materialize()).catalog))
  78. }),
  79. )
  80. const reordered = yield* Effect.scoped(
  81. Effect.gen(function* () {
  82. yield* codeMode.register(Tool.registrationEntries({ alpha, zeta }, { namespace: "tools" }))
  83. return yield* readUpdate(CodeModeInstructions.make((yield* codeMode.materialize()).catalog), initialized)
  84. }),
  85. )
  86. expect(reordered.changed).toBe(false)
  87. expect(reordered.text).toBe("")
  88. }).pipe(Effect.provide(layer))
  89. })
  90. })