80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
import {
|
|
DEFAULT_MAX_BYTES,
|
|
DEFAULT_MAX_LINES,
|
|
formatSize,
|
|
truncateHead,
|
|
} from "@mariozechner/pi-coding-agent";
|
|
import { Type } from "@sinclair/typebox";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
async function writeTempFile(content: string): Promise<string> {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-fetch-"));
|
|
const filePath = path.join(dir, "response.txt");
|
|
await fs.writeFile(filePath, content, "utf8");
|
|
return filePath;
|
|
}
|
|
|
|
export default function (pi: ExtensionAPI) {
|
|
pi.registerTool({
|
|
name: "fetch_url",
|
|
label: "Fetch URL",
|
|
description:
|
|
"Fetch a URL over HTTP(S) and return the response body as text (truncated to 50KB/2000 lines).",
|
|
parameters: Type.Object({
|
|
url: Type.String({ description: "URL to fetch" }),
|
|
timeoutMs: Type.Optional(
|
|
Type.Number({ description: "Timeout in milliseconds (default: 10000)" })
|
|
),
|
|
headers: Type.Optional(
|
|
Type.Record(Type.String(), Type.String(), {
|
|
description: "Optional request headers",
|
|
})
|
|
),
|
|
}),
|
|
async execute(_toolCallId, params, signal) {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(
|
|
() => controller.abort(),
|
|
params.timeoutMs ?? 10000
|
|
);
|
|
|
|
const combinedSignal = signal
|
|
? AbortSignal.any([signal, controller.signal])
|
|
: controller.signal;
|
|
|
|
try {
|
|
const response = await fetch(params.url, {
|
|
headers: params.headers,
|
|
signal: combinedSignal,
|
|
});
|
|
const body = await response.text();
|
|
const truncation = truncateHead(body, {
|
|
maxLines: DEFAULT_MAX_LINES,
|
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
});
|
|
|
|
let content = truncation.content;
|
|
if (truncation.truncated) {
|
|
const tempFile = await writeTempFile(body);
|
|
content += `\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). Full output saved to: ${tempFile}]`;
|
|
}
|
|
|
|
return {
|
|
content: [{ type: "text", text: content }],
|
|
details: {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
contentType: response.headers.get("content-type"),
|
|
url: response.url,
|
|
},
|
|
};
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
},
|
|
});
|
|
}
|