071e885d63
* Add utils for r2 upload * Add the tool and new routes * fix auth issue * sign headers * add comment * use our own API key to auth signed uploads * Restructure things slightly * tweak * tweak * add comments * tweak * revert a thing * twaek * drop mime type filtering * new incarnation of mime type filtering * jsut allow all octet-streams * simplify further * tweak * update lockfile --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import { type } from "arktype";
|
|
import { fileTypeFromBuffer } from "file-type";
|
|
import type { ToolContext } from "./server.ts";
|
|
import { execute, tool } from "./shared.ts";
|
|
|
|
const UploadFileParams = type({
|
|
path: type.string.describe("absolute path to file to upload"),
|
|
});
|
|
|
|
export function UploadFileTool(ctx: ToolContext) {
|
|
return tool({
|
|
name: "upload_file",
|
|
description:
|
|
"upload a file to get a permanent public URL. use for screenshots, artifacts, or any files you want to reference in PRs/comments. max 10MB, images/text/archives allowed.",
|
|
parameters: UploadFileParams,
|
|
execute: execute(async (params) => {
|
|
// read file from disk eagerly on purpose to avoid its content being changed by the time it's uploaded
|
|
const buffer = fs.readFileSync(params.path);
|
|
const filename = path.basename(params.path);
|
|
const contentLength = buffer.length;
|
|
|
|
const fileType = await fileTypeFromBuffer(buffer);
|
|
const contentType = fileType?.mime || "application/octet-stream";
|
|
|
|
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
|
|
|
const response = await fetch(`${apiUrl}/api/upload/signed-url`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${ctx.apiToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
filename,
|
|
contentType,
|
|
contentLength,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`failed to get upload URL: ${error}`);
|
|
}
|
|
|
|
const { uploadUrl, publicUrl } = (await response.json()) as {
|
|
uploadUrl: string;
|
|
publicUrl: string;
|
|
};
|
|
|
|
const uploadResponse = await fetch(uploadUrl, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": contentType,
|
|
// should be set automatically, but given this header is signed it's better to be explicit
|
|
"Content-Length": String(contentLength),
|
|
},
|
|
body: buffer,
|
|
});
|
|
|
|
if (!uploadResponse.ok) {
|
|
throw new Error(`failed to upload file: ${uploadResponse.statusText}`);
|
|
}
|
|
|
|
return { success: true, publicUrl, filename, contentLength, contentType };
|
|
}),
|
|
});
|
|
}
|