Enable LLMs to handle webhooks with plaintext files
1import type { LureHandlerOptions, LureHandler } from "./types.js";
2import type { StandardSchemaV1 } from "@standard-schema/spec";
3import { LureCache } from "./loader.js";
4import { watchLures } from "./watcher.js";
5import { createQueue } from "./queue.js";
6import type { FSWatcher } from "chokidar";
7
8export async function createLureHandler<TSchema extends StandardSchemaV1 | undefined = undefined>(
9 options: LureHandlerOptions<TSchema>,
10): Promise<LureHandler> {
11 const {
12 basePath: rawBasePath,
13 luresDir,
14 callback,
15 configSchema,
16 maxAttempts = 1,
17 allowUnverified = true,
18 watch = false,
19 } = options;
20
21 const basePath = "/" + rawBasePath.replace(/^\/+/, "").replace(/\/+$/, "");
22
23 const cache = new LureCache(
24 luresDir,
25 allowUnverified,
26 configSchema as StandardSchemaV1 | undefined,
27 );
28 await cache.load();
29
30 let watcher: FSWatcher | undefined;
31 if (watch) {
32 watcher = watchLures(luresDir, cache);
33 }
34
35 const queue = createQueue({
36 cache,
37 callback: callback as (prompt: string, config: unknown) => Promise<void>,
38 maxAttempts,
39 });
40
41 return {
42 handle(req) {
43 if (basePath === "/") {
44 // root basePath: every path matches; lurePath is the full path
45 } else if (req.path !== basePath && !req.path.startsWith(basePath + "/")) {
46 return false;
47 }
48
49 const lurePath = basePath === "/" ? req.path : req.path.slice(basePath.length) || "/";
50 const lure = cache.get(lurePath);
51 if (lure === undefined) {
52 return false;
53 }
54
55 queue.enqueue(lurePath, req);
56 return true;
57 },
58
59 async destroy() {
60 if (watcher !== undefined) {
61 await watcher.close();
62 }
63 await queue.drain();
64 },
65 };
66}