tool.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { describe, expect, test } from "bun:test"
  2. import { normalizeTool, toolInlineInfo, toolOutputText, toolPath, toolScroll } from "../../src/mini/tool"
  3. import { canonicalToolPart } from "./fixture/tool-part"
  4. describe("Mini tool presentation", () => {
  5. test("uses V2 shell output without the model-facing status", () => {
  6. expect(
  7. toolOutputText("shell", [
  8. { type: "text", text: "mini-output\n" },
  9. { type: "text", text: "Command exited with code 0." },
  10. ]),
  11. ).toBe("mini-output\n")
  12. expect(
  13. toolOutputText("shell", [
  14. { type: "text", text: "" },
  15. { type: "text", text: "Command exited with code 0." },
  16. ]),
  17. ).toBe("")
  18. })
  19. test("normalizes only persisted tool aliases into current fields", () => {
  20. expect(
  21. normalizeTool({
  22. type: "tool",
  23. id: "call-patch",
  24. name: "apply_patch",
  25. state: {
  26. status: "completed",
  27. input: { patchText: "*** Begin Patch\n*** End Patch" },
  28. metadata: {
  29. files: [
  30. {
  31. type: "update",
  32. filePath: "/tmp/project/src/a.ts",
  33. relativePath: "src/a.ts",
  34. patch: "@@ -1 +1 @@\n-old\n+new",
  35. },
  36. ],
  37. },
  38. content: [{ type: "text", text: "patched" }],
  39. },
  40. time: { created: 1, ran: 1, completed: 2 },
  41. }),
  42. ).toMatchObject({
  43. name: "patch",
  44. state: {
  45. metadata: {
  46. files: [
  47. {
  48. status: "modified",
  49. file: "src/a.ts",
  50. patch: "@@ -1 +1 @@\n-old\n+new",
  51. },
  52. ],
  53. },
  54. content: [{ type: "text", text: "patched" }],
  55. },
  56. })
  57. expect(
  58. normalizeTool({
  59. type: "tool",
  60. id: "call-subagent",
  61. name: "task",
  62. state: {
  63. status: "running",
  64. input: { subagent_type: "explore", description: "Inspect" },
  65. metadata: {},
  66. },
  67. time: { created: 1, ran: 1 },
  68. }),
  69. ).toMatchObject({ name: "subagent", state: { input: { agent: "explore" } } })
  70. })
  71. test("renders the skill name from tool metadata with the input id as fallback", () => {
  72. const skill = (metadata: { name?: string }) =>
  73. canonicalToolPart(
  74. "skill",
  75. {
  76. status: "completed",
  77. input: { id: "tigerstyle" },
  78. metadata,
  79. content: [{ type: "text", text: "" }],
  80. },
  81. "call-skill",
  82. )
  83. expect(toolInlineInfo(skill({ name: "effect" })).title).toBe('Skill "effect"')
  84. expect(toolInlineInfo(skill({})).title).toBe('Skill "tigerstyle"')
  85. expect(
  86. toolScroll("start", {
  87. directory: "/work/project",
  88. raw: "",
  89. name: "skill",
  90. input: { id: "tigerstyle" },
  91. meta: { name: "effect" },
  92. state: {},
  93. status: "completed",
  94. error: "",
  95. output: "",
  96. time: {},
  97. }),
  98. ).toBe('→ Skill "effect"')
  99. })
  100. test("renders compact search metadata", () => {
  101. expect(
  102. toolInlineInfo(
  103. canonicalToolPart("glob", {
  104. status: "completed",
  105. input: { pattern: "*.ts" },
  106. metadata: { count: 3 },
  107. content: [{ type: "text", text: "" }],
  108. }),
  109. ).description,
  110. ).toBe("3 matches")
  111. expect(
  112. toolInlineInfo(
  113. canonicalToolPart("grep", {
  114. status: "completed",
  115. input: { pattern: "needle" },
  116. metadata: { matches: 1 },
  117. content: [{ type: "text", text: "" }],
  118. }),
  119. ).description,
  120. ).toBe("1 match")
  121. })
  122. test("keeps segment-safe contained tool paths relative", () => {
  123. expect(toolPath("..cache/result.txt", { directory: "/work/project" })).toBe("..cache/result.txt")
  124. expect(toolPath("../shared/result.txt", { directory: "/work/project" })).toBe("/work/shared/result.txt")
  125. })
  126. })