1import type { ProviderUserInputAnswers, UserInputAttachments } from "@t3tools/contracts";
2import * as Schema from "effect/Schema";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import { resolveAttachmentPath } from "../attachmentStore.ts";
6class UserInputAttachmentError extends Schema.TaggedError<UserInputAttachmentError>()(
7 "UserInputAttachmentError",
8 {
9 operation: Schema.String,
10 issue: Schema.String,
11 cause: Schema.optional(Schema.Defect()),
12 },
13) {}
14
15const quoteReference = Schema.encodeSync(Schema.fromJsonString(Schema.String));
16
17/** Keep provider answer protocols unchanged; paths refer to files on the provider's server. */
18export const appendUserInputAttachmentPaths = Effect.fn("appendUserInputAttachmentPaths")(
19 function* (input: {
20 answers: ProviderUserInputAnswers;
21 attachmentsByQuestionId?: UserInputAttachments | undefined;
22 attachmentsDir: string;
23 }) {
24 const answers = new Map(Object.entries(input.answers));
25 const fs = yield* FileSystem.FileSystem;
26 for (const [questionId, attachments] of Object.entries(input.attachmentsByQuestionId ?? {})) {
27 if (attachments.length === 0) continue;
28 const references: string[] = [];
29 for (const attachment of attachments) {
30 const path = resolveAttachmentPath({ attachmentsDir: input.attachmentsDir, attachment });
31 if (
32 !path ||
33 !(yield* fs.exists(path).pipe(
34 Effect.mapError(
35 (cause) =>
36 new UserInputAttachmentError({
37 operation: "respondToUserInput",
38 issue: `Could not access attachment '${attachment.name}'.`,
39 cause,
40 }),
41 ),
42 ))
43 ) {
44 return yield* new UserInputAttachmentError({
45 operation: "respondToUserInput",
46 issue: `Attachment '${attachment.name}' is no longer available. Attach it again.`,
47 });
48 }
49 references.push(
50 `Attached ${attachment.type} ${quoteReference(attachment.name)}: ${quoteReference(path)}`,
51 );
52 }
53 const answer = answers.get(questionId);
54 const text = references.join("\n");
55 answers.set(
56 questionId,
57 Array.isArray(answer)
58 ? [...answer, text]
59 : typeof answer === "string" && answer.length > 0
60 ? `${answer}\n\n${text}`
61 : text,
62 );
63 }
64 return Object.fromEntries(answers);
65 },
66);