Skip to content
Source
Sign inSign up

ThreadTitleLinks.ts

44 lines1.6 KB

1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
4​
5const encodeSubject = Schema.encodeEffect(
6 Schema.fromJsonString(Schema.Struct({ title: Schema.String, body: Schema.String })),
7);
8​
9export const resolveThreadTitleLinks = Effect.fn("resolveThreadTitleLinks")(function* (input: {
10 message: string;
11 cwd: string;
12}) {
13 const providers = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
14 const links = new Map<string, NonNullable<ReturnType<typeof providers.resolveLink>>>();
15 for (const match of input.message.matchAll(/https:\/\/[^\s<>"')\]`]+/g)) {
16 const candidate = match[0].replace(/[.,:;!?*]+$/, "");
17 if (!URL.canParse(candidate)) continue;
18 const url = new URL(candidate);
19 url.hash = "";
20 url.search = "";
21 if (links.has(url.href)) continue;
22 const lookup = providers.resolveLink({ cwd: input.cwd, url });
23 if (!lookup) continue;
24 links.set(url.href, lookup);
25 if (links.size === 2) break;
26 }
27 const subjects = yield* Effect.forEach(
28 links,
29 ([url, lookup]) =>
30 lookup.pipe(
31 Effect.flatMap((subject) =>
32 encodeSubject({
33 title: subject.title.slice(0, 300),
34 body: subject.body?.slice(0, 1_200) ?? "",
35 }),
36 ),
37 Effect.map((summary) => `${url}\n${summary}`),
38 Effect.timeout("3 seconds"),
39 Effect.orElseSucceed(() => `${url}: unavailable`),
40 ),
41 { concurrency: 2 },
42 );
43 return subjects.length > 0 ? subjects.join("\n\n") : undefined;
44});