webfetch.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import { Instance } from "../../src/project/instance"
  4. import { WebFetchTool } from "../../src/tool/webfetch"
  5. import { SessionID, MessageID } from "../../src/session/schema"
  6. const projectRoot = path.join(import.meta.dir, "../..")
  7. const ctx = {
  8. sessionID: SessionID.make("ses_test"),
  9. messageID: MessageID.make("message"),
  10. callID: "",
  11. agent: "build",
  12. abort: AbortSignal.any([]),
  13. messages: [],
  14. metadata: () => {},
  15. ask: async () => {},
  16. }
  17. async function withFetch(
  18. mockFetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,
  19. fn: () => Promise<void>,
  20. ) {
  21. const originalFetch = globalThis.fetch
  22. globalThis.fetch = mockFetch as unknown as typeof fetch
  23. try {
  24. await fn()
  25. } finally {
  26. globalThis.fetch = originalFetch
  27. }
  28. }
  29. describe("tool.webfetch", () => {
  30. test("returns image responses as file attachments", async () => {
  31. const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])
  32. await withFetch(
  33. async () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }),
  34. async () => {
  35. await Instance.provide({
  36. directory: projectRoot,
  37. fn: async () => {
  38. const webfetch = await WebFetchTool.init()
  39. const result = await webfetch.execute({ url: "https://example.com/image.png", format: "markdown" }, ctx)
  40. expect(result.output).toBe("Image fetched successfully")
  41. expect(result.attachments).toBeDefined()
  42. expect(result.attachments?.length).toBe(1)
  43. expect(result.attachments?.[0].type).toBe("file")
  44. expect(result.attachments?.[0].mime).toBe("image/png")
  45. expect(result.attachments?.[0].url.startsWith("data:image/png;base64,")).toBe(true)
  46. expect(result.attachments?.[0]).not.toHaveProperty("id")
  47. expect(result.attachments?.[0]).not.toHaveProperty("sessionID")
  48. expect(result.attachments?.[0]).not.toHaveProperty("messageID")
  49. },
  50. })
  51. },
  52. )
  53. })
  54. test("keeps svg as text output", async () => {
  55. const svg = '<svg xmlns="http://www.w3.org/2000/svg"><text>hello</text></svg>'
  56. await withFetch(
  57. async () =>
  58. new Response(svg, {
  59. status: 200,
  60. headers: { "content-type": "image/svg+xml; charset=UTF-8" },
  61. }),
  62. async () => {
  63. await Instance.provide({
  64. directory: projectRoot,
  65. fn: async () => {
  66. const webfetch = await WebFetchTool.init()
  67. const result = await webfetch.execute({ url: "https://example.com/image.svg", format: "html" }, ctx)
  68. expect(result.output).toContain("<svg")
  69. expect(result.attachments).toBeUndefined()
  70. },
  71. })
  72. },
  73. )
  74. })
  75. test("keeps text responses as text output", async () => {
  76. await withFetch(
  77. async () =>
  78. new Response("hello from webfetch", {
  79. status: 200,
  80. headers: { "content-type": "text/plain; charset=utf-8" },
  81. }),
  82. async () => {
  83. await Instance.provide({
  84. directory: projectRoot,
  85. fn: async () => {
  86. const webfetch = await WebFetchTool.init()
  87. const result = await webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx)
  88. expect(result.output).toBe("hello from webfetch")
  89. expect(result.attachments).toBeUndefined()
  90. },
  91. })
  92. },
  93. )
  94. })
  95. })