hydrate files from thread root message on replies
When replying to a Slack thread, files attached to the root message were not being fetched. The existing `resolveSlackThreadStarter()` fetched the root message text via `conversations.replies` but ignored the `files[]` array in the response. Changes: - Add `files` to `SlackThreadStarter` type and extract from API response - Download thread starter files when the reply message has no attachments - Add verbose log for thread starter file hydration Fixes issue where asking about a PDF in a thread reply would fail because the model never received the file content from the root message.
This commit is contained in:
committed by
Peter Steinberger
parent
2accb47e4d
commit
578ac9f1a9
@@ -53,6 +53,7 @@ export type SlackThreadStarter = {
|
|||||||
text: string;
|
text: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
ts?: string;
|
ts?: string;
|
||||||
|
files?: SlackFile[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const THREAD_STARTER_CACHE = new Map<string, SlackThreadStarter>();
|
const THREAD_STARTER_CACHE = new Map<string, SlackThreadStarter>();
|
||||||
@@ -71,7 +72,7 @@ export async function resolveSlackThreadStarter(params: {
|
|||||||
ts: params.threadTs,
|
ts: params.threadTs,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
inclusive: true,
|
inclusive: true,
|
||||||
})) as { messages?: Array<{ text?: string; user?: string; ts?: string }> };
|
})) as { messages?: Array<{ text?: string; user?: string; ts?: string; files?: SlackFile[] }> };
|
||||||
const message = response?.messages?.[0];
|
const message = response?.messages?.[0];
|
||||||
const text = (message?.text ?? "").trim();
|
const text = (message?.text ?? "").trim();
|
||||||
if (!message || !text) return null;
|
if (!message || !text) return null;
|
||||||
@@ -79,6 +80,7 @@ export async function resolveSlackThreadStarter(params: {
|
|||||||
text,
|
text,
|
||||||
userId: message.user,
|
userId: message.user,
|
||||||
ts: message.ts,
|
ts: message.ts,
|
||||||
|
files: message.files,
|
||||||
};
|
};
|
||||||
THREAD_STARTER_CACHE.set(cacheKey, starter);
|
THREAD_STARTER_CACHE.set(cacheKey, starter);
|
||||||
return starter;
|
return starter;
|
||||||
|
|||||||
@@ -434,6 +434,7 @@ export async function prepareSlackMessage(params: {
|
|||||||
|
|
||||||
let threadStarterBody: string | undefined;
|
let threadStarterBody: string | undefined;
|
||||||
let threadLabel: string | undefined;
|
let threadLabel: string | undefined;
|
||||||
|
let threadStarterMedia: Awaited<ReturnType<typeof resolveSlackMedia>> = null;
|
||||||
if (isThreadReply && threadTs) {
|
if (isThreadReply && threadTs) {
|
||||||
const starter = await resolveSlackThreadStarter({
|
const starter = await resolveSlackThreadStarter({
|
||||||
channelId: message.channel,
|
channelId: message.channel,
|
||||||
@@ -453,11 +454,27 @@ export async function prepareSlackMessage(params: {
|
|||||||
});
|
});
|
||||||
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
||||||
threadLabel = `Slack thread ${roomLabel}${snippet ? `: ${snippet}` : ""}`;
|
threadLabel = `Slack thread ${roomLabel}${snippet ? `: ${snippet}` : ""}`;
|
||||||
|
// If current message has no files but thread starter does, fetch starter's files
|
||||||
|
if (!media && starter.files && starter.files.length > 0) {
|
||||||
|
threadStarterMedia = await resolveSlackMedia({
|
||||||
|
files: starter.files,
|
||||||
|
token: ctx.botToken,
|
||||||
|
maxBytes: ctx.mediaMaxBytes,
|
||||||
|
});
|
||||||
|
if (threadStarterMedia) {
|
||||||
|
logVerbose(
|
||||||
|
`slack: hydrated thread starter file ${threadStarterMedia.placeholder} from root message`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
threadLabel = `Slack thread ${roomLabel}`;
|
threadLabel = `Slack thread ${roomLabel}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use thread starter media if current message has none
|
||||||
|
const effectiveMedia = media ?? threadStarterMedia;
|
||||||
|
|
||||||
const ctxPayload = finalizeInboundContext({
|
const ctxPayload = finalizeInboundContext({
|
||||||
Body: combinedBody,
|
Body: combinedBody,
|
||||||
RawBody: rawBody,
|
RawBody: rawBody,
|
||||||
@@ -483,9 +500,9 @@ export async function prepareSlackMessage(params: {
|
|||||||
ThreadLabel: threadLabel,
|
ThreadLabel: threadLabel,
|
||||||
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
||||||
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
||||||
MediaPath: media?.path,
|
MediaPath: effectiveMedia?.path,
|
||||||
MediaType: media?.contentType,
|
MediaType: effectiveMedia?.contentType,
|
||||||
MediaUrl: media?.path,
|
MediaUrl: effectiveMedia?.path,
|
||||||
CommandAuthorized: commandAuthorized,
|
CommandAuthorized: commandAuthorized,
|
||||||
OriginatingChannel: "slack" as const,
|
OriginatingChannel: "slack" as const,
|
||||||
OriginatingTo: slackTo,
|
OriginatingTo: slackTo,
|
||||||
|
|||||||
Reference in New Issue
Block a user