| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import { describe, expect, test } from "bun:test"
- import path from "path"
- import { Instance } from "../../src/project/instance"
- import { WebFetchTool } from "../../src/tool/webfetch"
- import { SessionID, MessageID } from "../../src/session/schema"
- const projectRoot = path.join(import.meta.dir, "../..")
- const ctx = {
- sessionID: SessionID.make("ses_test"),
- messageID: MessageID.make("message"),
- callID: "",
- agent: "build",
- abort: AbortSignal.any([]),
- messages: [],
- metadata: () => {},
- ask: async () => {},
- }
- type TimerID = ReturnType<typeof setTimeout>
- async function withFetch(
- mockFetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,
- fn: () => Promise<void>,
- ) {
- const originalFetch = globalThis.fetch
- globalThis.fetch = mockFetch as unknown as typeof fetch
- try {
- await fn()
- } finally {
- globalThis.fetch = originalFetch
- }
- }
- async function withTimers(fn: (state: { ids: TimerID[]; cleared: TimerID[] }) => Promise<void>) {
- const set = globalThis.setTimeout
- const clear = globalThis.clearTimeout
- const ids: TimerID[] = []
- const cleared: TimerID[] = []
- globalThis.setTimeout = ((...args: Parameters<typeof setTimeout>) => {
- const id = set(...args)
- ids.push(id)
- return id
- }) as typeof setTimeout
- globalThis.clearTimeout = ((id?: TimerID) => {
- if (id !== undefined) cleared.push(id)
- return clear(id)
- }) as typeof clearTimeout
- try {
- await fn({ ids, cleared })
- } finally {
- ids.forEach(clear)
- globalThis.setTimeout = set
- globalThis.clearTimeout = clear
- }
- }
- describe("tool.webfetch", () => {
- test("returns image responses as file attachments", async () => {
- const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])
- await withFetch(
- async () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }),
- async () => {
- await Instance.provide({
- directory: projectRoot,
- fn: async () => {
- const webfetch = await WebFetchTool.init()
- const result = await webfetch.execute({ url: "https://example.com/image.png", format: "markdown" }, ctx)
- expect(result.output).toBe("Image fetched successfully")
- expect(result.attachments).toBeDefined()
- expect(result.attachments?.length).toBe(1)
- expect(result.attachments?.[0].type).toBe("file")
- expect(result.attachments?.[0].mime).toBe("image/png")
- expect(result.attachments?.[0].url.startsWith("data:image/png;base64,")).toBe(true)
- expect(result.attachments?.[0]).not.toHaveProperty("id")
- expect(result.attachments?.[0]).not.toHaveProperty("sessionID")
- expect(result.attachments?.[0]).not.toHaveProperty("messageID")
- },
- })
- },
- )
- })
- test("keeps svg as text output", async () => {
- const svg = '<svg xmlns="http://www.w3.org/2000/svg"><text>hello</text></svg>'
- await withFetch(
- async () =>
- new Response(svg, {
- status: 200,
- headers: { "content-type": "image/svg+xml; charset=UTF-8" },
- }),
- async () => {
- await Instance.provide({
- directory: projectRoot,
- fn: async () => {
- const webfetch = await WebFetchTool.init()
- const result = await webfetch.execute({ url: "https://example.com/image.svg", format: "html" }, ctx)
- expect(result.output).toContain("<svg")
- expect(result.attachments).toBeUndefined()
- },
- })
- },
- )
- })
- test("keeps text responses as text output", async () => {
- await withFetch(
- async () =>
- new Response("hello from webfetch", {
- status: 200,
- headers: { "content-type": "text/plain; charset=utf-8" },
- }),
- async () => {
- await Instance.provide({
- directory: projectRoot,
- fn: async () => {
- const webfetch = await WebFetchTool.init()
- const result = await webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx)
- expect(result.output).toBe("hello from webfetch")
- expect(result.attachments).toBeUndefined()
- },
- })
- },
- )
- })
- test("clears timeout when fetch rejects", async () => {
- await withTimers(async ({ ids, cleared }) => {
- await withFetch(
- async () => {
- throw new Error("boom")
- },
- async () => {
- await Instance.provide({
- directory: projectRoot,
- fn: async () => {
- const webfetch = await WebFetchTool.init()
- await expect(
- webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx),
- ).rejects.toThrow("boom")
- },
- })
- },
- )
- expect(ids).toHaveLength(1)
- expect(cleared).toHaveLength(1)
- expect(cleared[0]).toBe(ids[0])
- })
- })
- })
|