@automagik/omni 2.260624.2 → 2.260624.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -3
- package/dist/server/index.js +223 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62387,7 +62387,8 @@ class AgnoAgentProvider {
|
|
|
62387
62387
|
});
|
|
62388
62388
|
const response = await this.client.run(request);
|
|
62389
62389
|
const customerContent = toSafeCustomerFallback(response.content);
|
|
62390
|
-
|
|
62390
|
+
const customerErrorBlocked = customerContent !== response.content;
|
|
62391
|
+
if (customerErrorBlocked) {
|
|
62391
62392
|
log8.error("Blocked provider error from customer-facing Agno response", {
|
|
62392
62393
|
agentId: this.config.agentId,
|
|
62393
62394
|
runId: response.runId,
|
|
@@ -62414,7 +62415,8 @@ class AgnoAgentProvider {
|
|
|
62414
62415
|
cost: response.metrics ? {
|
|
62415
62416
|
inputTokens: response.metrics.inputTokens,
|
|
62416
62417
|
outputTokens: response.metrics.outputTokens
|
|
62417
|
-
} : undefined
|
|
62418
|
+
} : undefined,
|
|
62419
|
+
customerErrorBlocked
|
|
62418
62420
|
}
|
|
62419
62421
|
};
|
|
62420
62422
|
}
|
|
@@ -125002,7 +125004,7 @@ import { fileURLToPath } from "url";
|
|
|
125002
125004
|
// package.json
|
|
125003
125005
|
var package_default = {
|
|
125004
125006
|
name: "@automagik/omni",
|
|
125005
|
-
version: "2.260624.
|
|
125007
|
+
version: "2.260624.4",
|
|
125006
125008
|
description: "LLM-optimized CLI for Omni",
|
|
125007
125009
|
type: "module",
|
|
125008
125010
|
bin: {
|
package/dist/server/index.js
CHANGED
|
@@ -30720,7 +30720,8 @@ class AgnoAgentProvider {
|
|
|
30720
30720
|
});
|
|
30721
30721
|
const response = await this.client.run(request);
|
|
30722
30722
|
const customerContent = toSafeCustomerFallback(response.content);
|
|
30723
|
-
|
|
30723
|
+
const customerErrorBlocked = customerContent !== response.content;
|
|
30724
|
+
if (customerErrorBlocked) {
|
|
30724
30725
|
log8.error("Blocked provider error from customer-facing Agno response", {
|
|
30725
30726
|
agentId: this.config.agentId,
|
|
30726
30727
|
runId: response.runId,
|
|
@@ -30747,7 +30748,8 @@ class AgnoAgentProvider {
|
|
|
30747
30748
|
cost: response.metrics ? {
|
|
30748
30749
|
inputTokens: response.metrics.inputTokens,
|
|
30749
30750
|
outputTokens: response.metrics.outputTokens
|
|
30750
|
-
} : undefined
|
|
30751
|
+
} : undefined,
|
|
30752
|
+
customerErrorBlocked
|
|
30751
30753
|
}
|
|
30752
30754
|
};
|
|
30753
30755
|
}
|
|
@@ -225289,7 +225291,7 @@ var init_sentry_scrub = __esm(() => {
|
|
|
225289
225291
|
var require_package7 = __commonJS((exports, module) => {
|
|
225290
225292
|
module.exports = {
|
|
225291
225293
|
name: "@omni/api",
|
|
225292
|
-
version: "2.260624.
|
|
225294
|
+
version: "2.260624.4",
|
|
225293
225295
|
type: "module",
|
|
225294
225296
|
exports: {
|
|
225295
225297
|
".": {
|
|
@@ -332955,6 +332957,62 @@ var init_batch_pricing = __esm(() => {
|
|
|
332955
332957
|
// ../api/src/services/media-storage.ts
|
|
332956
332958
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
332957
332959
|
import { dirname as dirname6, extname as extname2, join as join21, relative as relative2 } from "path";
|
|
332960
|
+
function normalizeMimeType2(value) {
|
|
332961
|
+
return value?.split(";")[0]?.trim().toLowerCase() || undefined;
|
|
332962
|
+
}
|
|
332963
|
+
function isHtmlMime2(value) {
|
|
332964
|
+
const mimeType = normalizeMimeType2(value);
|
|
332965
|
+
return mimeType === "text/html" || mimeType === "application/xhtml+xml";
|
|
332966
|
+
}
|
|
332967
|
+
function looksLikeHtml2(buffer3) {
|
|
332968
|
+
const preview = buffer3.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
|
|
332969
|
+
return preview.startsWith("<!doctype html") || preview.startsWith("<html>") || preview.startsWith("<html ");
|
|
332970
|
+
}
|
|
332971
|
+
function shouldRejectHtmlMedia(expectedMimeType, responseMimeType, buffer3) {
|
|
332972
|
+
const expected = normalizeMimeType2(expectedMimeType);
|
|
332973
|
+
if (!expected || isHtmlMime2(expected))
|
|
332974
|
+
return false;
|
|
332975
|
+
return isHtmlMime2(responseMimeType) || looksLikeHtml2(buffer3);
|
|
332976
|
+
}
|
|
332977
|
+
function hostMatchesSuffix2(hostname3, suffix) {
|
|
332978
|
+
const normalizedHost = hostname3.toLowerCase();
|
|
332979
|
+
const normalizedSuffix = suffix.toLowerCase();
|
|
332980
|
+
return normalizedHost === normalizedSuffix || normalizedHost.endsWith(`.${normalizedSuffix}`);
|
|
332981
|
+
}
|
|
332982
|
+
function shouldPreserveAuthForRedirect(url, suffixes) {
|
|
332983
|
+
return Boolean(suffixes?.some((suffix) => hostMatchesSuffix2(url.hostname, suffix)));
|
|
332984
|
+
}
|
|
332985
|
+
function headersWithOptionalAuthorization2(headers, preserveAuthorization) {
|
|
332986
|
+
const nextHeaders = new Headers(headers);
|
|
332987
|
+
if (!preserveAuthorization)
|
|
332988
|
+
nextHeaders.delete("authorization");
|
|
332989
|
+
return nextHeaders;
|
|
332990
|
+
}
|
|
332991
|
+
async function fetchWithOptionalAuthenticatedRedirects(url, fetchOptions) {
|
|
332992
|
+
const { preserveAuthRedirectHostSuffixes, ...init5 } = fetchOptions ?? {};
|
|
332993
|
+
if (!preserveAuthRedirectHostSuffixes?.length) {
|
|
332994
|
+
return fetch(url, init5);
|
|
332995
|
+
}
|
|
332996
|
+
let currentUrl = new URL(url);
|
|
332997
|
+
let currentHeaders = new Headers(init5.headers);
|
|
332998
|
+
for (let redirects = 0;redirects <= 5; redirects++) {
|
|
332999
|
+
const response = await fetch(currentUrl.toString(), {
|
|
333000
|
+
...init5,
|
|
333001
|
+
headers: currentHeaders,
|
|
333002
|
+
redirect: "manual"
|
|
333003
|
+
});
|
|
333004
|
+
if (response.status < 300 || response.status >= 400)
|
|
333005
|
+
return response;
|
|
333006
|
+
const location = response.headers.get("location");
|
|
333007
|
+
if (!location)
|
|
333008
|
+
return response;
|
|
333009
|
+
const nextUrl = new URL(location, currentUrl);
|
|
333010
|
+
const preserveAuthorization = shouldPreserveAuthForRedirect(currentUrl, preserveAuthRedirectHostSuffixes) && shouldPreserveAuthForRedirect(nextUrl, preserveAuthRedirectHostSuffixes);
|
|
333011
|
+
currentHeaders = headersWithOptionalAuthorization2(init5.headers, preserveAuthorization);
|
|
333012
|
+
currentUrl = nextUrl;
|
|
333013
|
+
}
|
|
333014
|
+
throw new Error("Failed to download media: too many redirects");
|
|
333015
|
+
}
|
|
332958
333016
|
function getExtensionFromMime2(mimeType) {
|
|
332959
333017
|
const mimeToExt = {
|
|
332960
333018
|
"image/jpeg": ".jpg",
|
|
@@ -333043,12 +333101,16 @@ class MediaStorageService {
|
|
|
333043
333101
|
};
|
|
333044
333102
|
}
|
|
333045
333103
|
async storeFromUrl(instanceId, messageId, url, mimeType, timestamp3, fetchOptions) {
|
|
333046
|
-
const response = await
|
|
333104
|
+
const response = await fetchWithOptionalAuthenticatedRedirects(url, fetchOptions);
|
|
333047
333105
|
if (!response.ok) {
|
|
333048
333106
|
throw new Error(`Failed to download media: ${response.status}`);
|
|
333049
333107
|
}
|
|
333108
|
+
const responseContentType = response.headers.get("content-type") ?? undefined;
|
|
333050
333109
|
const buffer3 = Buffer.from(await response.arrayBuffer());
|
|
333051
|
-
|
|
333110
|
+
if (shouldRejectHtmlMedia(mimeType, responseContentType, buffer3)) {
|
|
333111
|
+
throw new Error(`Downloaded media content mismatch: expected ${mimeType}, received ${responseContentType ?? "unknown content type"}`);
|
|
333112
|
+
}
|
|
333113
|
+
const contentType = mimeType ?? responseContentType;
|
|
333052
333114
|
return this.storeFromBuffer(instanceId, messageId, buffer3, contentType, timestamp3);
|
|
333053
333115
|
}
|
|
333054
333116
|
async updateMessageLocalPath(messageId, localPath) {
|
|
@@ -338466,6 +338528,68 @@ async function sendErrorFeedback(channel5, instanceId, chatId, error3, message2)
|
|
|
338466
338528
|
log99.error("agent_dispatch_error", { channel: channel5, instanceId, chatId, error: errorMsg });
|
|
338467
338529
|
await sendTextMessage5(channel5, instanceId, chatId, message2);
|
|
338468
338530
|
}
|
|
338531
|
+
function resolveErrorHandoffMessage(env2 = process.env) {
|
|
338532
|
+
return env2.OMNI_AGENT_ERROR_HANDOFF_MESSAGE?.trim() || DEFAULT_ERROR_HANDOFF_MESSAGE;
|
|
338533
|
+
}
|
|
338534
|
+
async function triggerErrorHandoff(services, db2, channel5, instance4, chatId, message2) {
|
|
338535
|
+
const plugin7 = await getPlugin(channel5);
|
|
338536
|
+
if (plugin7?.capabilities?.canHandoff !== true)
|
|
338537
|
+
return false;
|
|
338538
|
+
let sendResult;
|
|
338539
|
+
try {
|
|
338540
|
+
sendResult = await plugin7.sendMessage(instance4.id, {
|
|
338541
|
+
to: chatId,
|
|
338542
|
+
content: { type: "text", text: message2 },
|
|
338543
|
+
metadata: { isHandoff: true, motivoHandoff: "agent_dispatch_error" }
|
|
338544
|
+
});
|
|
338545
|
+
} catch (err) {
|
|
338546
|
+
log99.error("agent_dispatch_error_handoff_failed", { instanceId: instance4.id, chatId, error: String(err) });
|
|
338547
|
+
return false;
|
|
338548
|
+
}
|
|
338549
|
+
if (!sendResult?.success) {
|
|
338550
|
+
log99.error("agent_dispatch_error_handoff_delivery_failed", {
|
|
338551
|
+
instanceId: instance4.id,
|
|
338552
|
+
chatId,
|
|
338553
|
+
error: sendResult?.error
|
|
338554
|
+
});
|
|
338555
|
+
return false;
|
|
338556
|
+
}
|
|
338557
|
+
try {
|
|
338558
|
+
const chat2 = await services.chats.findByExternalIdSmart(instance4.id, chatId);
|
|
338559
|
+
if (chat2) {
|
|
338560
|
+
const settings = chat2.settings ?? {};
|
|
338561
|
+
await services.chats.update(chat2.id, { settings: { ...settings, agentPaused: true } });
|
|
338562
|
+
await services.followUpLifecycle.disarm({ chatId: chat2.id, instanceId: instance4.id, reason: "handoff" });
|
|
338563
|
+
await db2.insert(handoffLogs).values({
|
|
338564
|
+
instanceId: instance4.id,
|
|
338565
|
+
chatUuid: chat2.id,
|
|
338566
|
+
chatId,
|
|
338567
|
+
toPhone: chatId,
|
|
338568
|
+
text: message2,
|
|
338569
|
+
extraInfo: null,
|
|
338570
|
+
agentId: instance4.agentId ?? null,
|
|
338571
|
+
externalMessageId: sendResult?.messageId ?? null,
|
|
338572
|
+
handoffFields: null,
|
|
338573
|
+
sentAt: new Date,
|
|
338574
|
+
metadata: { instanceChannel: channel5, channelHandoffSupported: true, motivoHandoff: "agent_dispatch_error" }
|
|
338575
|
+
});
|
|
338576
|
+
}
|
|
338577
|
+
} catch (err) {
|
|
338578
|
+
log99.warn("agent_dispatch_error_handoff_side_effects_failed", {
|
|
338579
|
+
instanceId: instance4.id,
|
|
338580
|
+
chatId,
|
|
338581
|
+
error: String(err)
|
|
338582
|
+
});
|
|
338583
|
+
}
|
|
338584
|
+
log99.info("agent_dispatch_error_handoff", { instanceId: instance4.id, chatId, channel: channel5 });
|
|
338585
|
+
return true;
|
|
338586
|
+
}
|
|
338587
|
+
async function handleDispatchFailure(services, db2, channel5, instance4, chatId, error3) {
|
|
338588
|
+
const handedOff = await triggerErrorHandoff(services, db2, channel5, instance4, chatId, resolveErrorHandoffMessage());
|
|
338589
|
+
if (handedOff)
|
|
338590
|
+
return;
|
|
338591
|
+
await sendErrorFeedback(channel5, instance4.id, chatId, error3, resolveDispatchErrorMessage(null)).catch(() => {});
|
|
338592
|
+
}
|
|
338469
338593
|
function sleep5(ms) {
|
|
338470
338594
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
338471
338595
|
}
|
|
@@ -339567,7 +339691,11 @@ async function dispatchViaProvider(services, instance4, messages4, triggerType,
|
|
|
339567
339691
|
}
|
|
339568
339692
|
const chatAfterRun = await services.chats.findByExternalIdSmart(instance4.id, chatId);
|
|
339569
339693
|
const handoffTriggered = chatAfterRun?.settings?.agentPaused === true;
|
|
339570
|
-
|
|
339694
|
+
let errorHandoffDone = false;
|
|
339695
|
+
if (result?.metadata.customerErrorBlocked === true && !handoffTriggered) {
|
|
339696
|
+
errorHandoffDone = await triggerErrorHandoff(services, db2, channel5, instance4, chatId, resolveErrorHandoffMessage());
|
|
339697
|
+
}
|
|
339698
|
+
if (result && result.parts.length > 0 && !handoffTriggered && !errorHandoffDone) {
|
|
339571
339699
|
const selfChat = isSelfChat(chatId, instance4.ownerIdentifier);
|
|
339572
339700
|
const rawParts = selfChat ? result.parts.map((p2) => `${BOT_PREFIX}${p2}`) : result.parts;
|
|
339573
339701
|
const parts = await Promise.all(rawParts.map((part) => executeBeforeMessageWriteHooks(instance4.id, chatId, part)));
|
|
@@ -340071,7 +340199,7 @@ async function processAgentResponse(services, instance4, messages4, triggerType,
|
|
|
340071
340199
|
error: String(error3),
|
|
340072
340200
|
traceId
|
|
340073
340201
|
});
|
|
340074
|
-
|
|
340202
|
+
await handleDispatchFailure(services, db2, channel5, instance4, chatId, error3);
|
|
340075
340203
|
} finally {
|
|
340076
340204
|
ackHandle.remove();
|
|
340077
340205
|
}
|
|
@@ -341198,7 +341326,7 @@ async function setupAgentDispatcher(eventBus, services, db2) {
|
|
|
341198
341326
|
log99.info("Agent dispatcher shutdown complete");
|
|
341199
341327
|
};
|
|
341200
341328
|
}
|
|
341201
|
-
var import_api32, log99, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, DEFAULT_DISPATCH_ERROR_MESSAGE = "\u26A0\uFE0F Sorry, I ran into an issue processing your message. Please try again.", TRANSIENT_DISPATCH_ERROR_PATTERNS, TRANSIENT_DISPATCH_RETRY_DELAYS_MS, CHANNEL_MESSAGE_LIMITS, DEFAULT_MESSAGE_LIMIT = 4000, MEDIA_BASE_PATH3, MEDIA_ICONS, MEDIA_WAIT_NULL, mediaCompletions, mediaResultCache, MEDIA_WAIT_TIMEOUT_MS = 30000, DEFAULT_SEND_MEDIA_PATH_TYPES, BOT_PREFIX = "\uD83E\uDD16 ", activeStreams, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", providerCache, openclawClientPool, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
|
|
341329
|
+
var import_api32, log99, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, DEFAULT_DISPATCH_ERROR_MESSAGE = "\u26A0\uFE0F Sorry, I ran into an issue processing your message. Please try again.", DEFAULT_ERROR_HANDOFF_MESSAGE = "T\xF4 com um probleminha t\xE9cnico aqui agora. Logo algu\xE9m do time vai entrar em contato com voc\xEA.", TRANSIENT_DISPATCH_ERROR_PATTERNS, TRANSIENT_DISPATCH_RETRY_DELAYS_MS, CHANNEL_MESSAGE_LIMITS, DEFAULT_MESSAGE_LIMIT = 4000, MEDIA_BASE_PATH3, MEDIA_ICONS, MEDIA_WAIT_NULL, mediaCompletions, mediaResultCache, MEDIA_WAIT_TIMEOUT_MS = 30000, DEFAULT_SEND_MEDIA_PATH_TYPES, BOT_PREFIX = "\uD83E\uDD16 ", activeStreams, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", providerCache, openclawClientPool, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
|
|
341202
341330
|
var init_agent_dispatcher = __esm(() => {
|
|
341203
341331
|
init_src2();
|
|
341204
341332
|
init_src();
|
|
@@ -356723,6 +356851,27 @@ async function resolveMessageFromRef(services, ref) {
|
|
|
356723
356851
|
if ("messageId" in ref) {
|
|
356724
356852
|
return services.messages.getById(ref.messageId);
|
|
356725
356853
|
}
|
|
356854
|
+
if ("chatExternalId" in ref) {
|
|
356855
|
+
const chat2 = await services.chats.findByExternalIdSmart(ref.instanceId, ref.chatExternalId);
|
|
356856
|
+
if (!chat2) {
|
|
356857
|
+
throw new OmniError({
|
|
356858
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
356859
|
+
message: `Chat not found for instanceId=${ref.instanceId}, chatExternalId=${ref.chatExternalId}`,
|
|
356860
|
+
context: { instanceId: ref.instanceId, chatExternalId: ref.chatExternalId },
|
|
356861
|
+
recoverable: false
|
|
356862
|
+
});
|
|
356863
|
+
}
|
|
356864
|
+
const found2 = await services.messages.getByExternalId(chat2.id, ref.externalId);
|
|
356865
|
+
if (!found2) {
|
|
356866
|
+
throw new OmniError({
|
|
356867
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
356868
|
+
message: `Message not found for chatExternalId=${ref.chatExternalId}, externalId=${ref.externalId}`,
|
|
356869
|
+
context: { instanceId: ref.instanceId, chatExternalId: ref.chatExternalId, externalId: ref.externalId },
|
|
356870
|
+
recoverable: false
|
|
356871
|
+
});
|
|
356872
|
+
}
|
|
356873
|
+
return found2;
|
|
356874
|
+
}
|
|
356726
356875
|
const found = await services.messages.getByExternalId(ref.chatId, ref.externalId);
|
|
356727
356876
|
if (!found) {
|
|
356728
356877
|
throw new OmniError({
|
|
@@ -356734,6 +356883,17 @@ async function resolveMessageFromRef(services, ref) {
|
|
|
356734
356883
|
}
|
|
356735
356884
|
return found;
|
|
356736
356885
|
}
|
|
356886
|
+
function buildMediaDownloadFetchOptions(instance4) {
|
|
356887
|
+
if (instance4.channel !== "slack")
|
|
356888
|
+
return;
|
|
356889
|
+
const slackBotToken = typeof instance4.slackBotToken === "string" ? instance4.slackBotToken : undefined;
|
|
356890
|
+
if (!slackBotToken)
|
|
356891
|
+
return;
|
|
356892
|
+
return {
|
|
356893
|
+
headers: { Authorization: `Bearer ${slackBotToken}` },
|
|
356894
|
+
preserveAuthRedirectHostSuffixes: ["slack.com"]
|
|
356895
|
+
};
|
|
356896
|
+
}
|
|
356737
356897
|
async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRowId) {
|
|
356738
356898
|
const cfg = resolveCloseContactConfig(outcome, null);
|
|
356739
356899
|
if (isHardTerminalOutcome(outcome)) {
|
|
@@ -357021,7 +357181,8 @@ var init_messages5 = __esm(() => {
|
|
|
357021
357181
|
});
|
|
357022
357182
|
messageRefSchema = exports_external.union([
|
|
357023
357183
|
exports_external.object({ messageId: exports_external.string().uuid() }),
|
|
357024
|
-
exports_external.object({ chatId: exports_external.string().uuid(), externalId: exports_external.string().min(1) })
|
|
357184
|
+
exports_external.object({ chatId: exports_external.string().uuid(), externalId: exports_external.string().min(1) }),
|
|
357185
|
+
exports_external.object({ instanceId: exports_external.string().uuid(), chatExternalId: exports_external.string().min(1), externalId: exports_external.string().min(1) })
|
|
357025
357186
|
]);
|
|
357026
357187
|
messagesRoutes.post("/media/download", zValidator("json", messageRefSchema), async (c) => {
|
|
357027
357188
|
const body = c.req.valid("json");
|
|
@@ -357039,6 +357200,7 @@ var init_messages5 = __esm(() => {
|
|
|
357039
357200
|
recoverable: false
|
|
357040
357201
|
});
|
|
357041
357202
|
}
|
|
357203
|
+
const instance4 = await services.instances.getById(instanceId);
|
|
357042
357204
|
checkInstanceAccess2(apiKey, instanceId);
|
|
357043
357205
|
if (!message2.hasMedia || !message2.mediaUrl) {
|
|
357044
357206
|
return c.json({
|
|
@@ -357066,7 +357228,7 @@ var init_messages5 = __esm(() => {
|
|
|
357066
357228
|
}
|
|
357067
357229
|
if (!cached) {
|
|
357068
357230
|
try {
|
|
357069
|
-
const result = await mediaStorage2.storeFromUrl(instanceId, message2.id, mediaUrl, message2.mediaMimeType ?? undefined, message2.platformTimestamp ?? undefined);
|
|
357231
|
+
const result = await mediaStorage2.storeFromUrl(instanceId, message2.id, mediaUrl, message2.mediaMimeType ?? undefined, message2.platformTimestamp ?? undefined, buildMediaDownloadFetchOptions(instance4));
|
|
357070
357232
|
mediaLocalPath = result.localPath;
|
|
357071
357233
|
await mediaStorage2.updateMessageLocalPath(message2.id, result.localPath);
|
|
357072
357234
|
mediaDownloadLog.info("Downloaded and cached media", {
|
|
@@ -357087,7 +357249,7 @@ var init_messages5 = __esm(() => {
|
|
|
357087
357249
|
});
|
|
357088
357250
|
}
|
|
357089
357251
|
}
|
|
357090
|
-
const downloadUrl = `/api/v2/media/${
|
|
357252
|
+
const downloadUrl = `/api/v2/media/${mediaLocalPath}`;
|
|
357091
357253
|
return c.json({
|
|
357092
357254
|
data: {
|
|
357093
357255
|
messageId: message2.id,
|
|
@@ -367738,6 +367900,51 @@ function setupCommandHandlers(app, instanceId, commandNames, callbacks, logger5)
|
|
|
367738
367900
|
init_src2();
|
|
367739
367901
|
init_types10();
|
|
367740
367902
|
var downloadGuard2 = createDownloadGuard();
|
|
367903
|
+
function normalizeMimeType(value) {
|
|
367904
|
+
return value?.split(";")[0]?.trim().toLowerCase() || undefined;
|
|
367905
|
+
}
|
|
367906
|
+
function isHtmlMime(value) {
|
|
367907
|
+
const mimeType = normalizeMimeType(value);
|
|
367908
|
+
return mimeType === "text/html" || mimeType === "application/xhtml+xml";
|
|
367909
|
+
}
|
|
367910
|
+
function looksLikeHtml(buffer2) {
|
|
367911
|
+
const preview = buffer2.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
|
|
367912
|
+
return preview.startsWith("<!doctype html") || preview.startsWith("<html>") || preview.startsWith("<html ");
|
|
367913
|
+
}
|
|
367914
|
+
function hostMatchesSuffix(hostname, suffix) {
|
|
367915
|
+
const normalizedHost = hostname.toLowerCase();
|
|
367916
|
+
const normalizedSuffix = suffix.toLowerCase();
|
|
367917
|
+
return normalizedHost === normalizedSuffix || normalizedHost.endsWith(`.${normalizedSuffix}`);
|
|
367918
|
+
}
|
|
367919
|
+
function isSlackHost(url) {
|
|
367920
|
+
return hostMatchesSuffix(url.hostname, "slack.com");
|
|
367921
|
+
}
|
|
367922
|
+
function headersWithOptionalAuthorization(botToken, preserveAuthorization) {
|
|
367923
|
+
const headers = new Headers;
|
|
367924
|
+
if (preserveAuthorization)
|
|
367925
|
+
headers.set("Authorization", `Bearer ${botToken}`);
|
|
367926
|
+
headers.set("Accept", "application/octet-stream");
|
|
367927
|
+
return headers;
|
|
367928
|
+
}
|
|
367929
|
+
async function fetchSlackPrivateUrl(url, botToken) {
|
|
367930
|
+
let currentUrl = new URL(url);
|
|
367931
|
+
let preserveAuthorization = true;
|
|
367932
|
+
for (let redirects = 0;redirects <= 5; redirects++) {
|
|
367933
|
+
const response = await fetch(currentUrl.toString(), {
|
|
367934
|
+
headers: headersWithOptionalAuthorization(botToken, preserveAuthorization),
|
|
367935
|
+
redirect: "manual"
|
|
367936
|
+
});
|
|
367937
|
+
if (response.status < 300 || response.status >= 400)
|
|
367938
|
+
return response;
|
|
367939
|
+
const location = response.headers.get("location");
|
|
367940
|
+
if (!location)
|
|
367941
|
+
return response;
|
|
367942
|
+
const nextUrl = new URL(location, currentUrl);
|
|
367943
|
+
preserveAuthorization = isSlackHost(currentUrl) && isSlackHost(nextUrl);
|
|
367944
|
+
currentUrl = nextUrl;
|
|
367945
|
+
}
|
|
367946
|
+
throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, "Failed to download file: too many redirects");
|
|
367947
|
+
}
|
|
367741
367948
|
function extractFileInfo(files) {
|
|
367742
367949
|
if (!files || !Array.isArray(files))
|
|
367743
367950
|
return [];
|
|
@@ -367754,20 +367961,19 @@ function extractFileInfo(files) {
|
|
|
367754
367961
|
};
|
|
367755
367962
|
});
|
|
367756
367963
|
}
|
|
367757
|
-
async function downloadSlackFile(url, botToken, logger5) {
|
|
367964
|
+
async function downloadSlackFile(url, botToken, logger5, expectedMimeType) {
|
|
367758
367965
|
logger5.debug("Downloading file from Slack CDN", { url: url.substring(0, 50) });
|
|
367759
367966
|
try {
|
|
367760
|
-
const response = await
|
|
367761
|
-
headers: {
|
|
367762
|
-
Authorization: `Bearer ${botToken}`
|
|
367763
|
-
}
|
|
367764
|
-
});
|
|
367967
|
+
const response = await fetchSlackPrivateUrl(url, botToken);
|
|
367765
367968
|
if (!response.ok) {
|
|
367766
367969
|
throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, `Failed to download file: HTTP ${response.status} ${response.statusText}`);
|
|
367767
367970
|
}
|
|
367768
367971
|
downloadGuard2.checkResponse(response, logger5, { channel: "slack", url: url.substring(0, 50) });
|
|
367769
367972
|
const buffer2 = Buffer.from(await response.arrayBuffer());
|
|
367770
367973
|
const mimeType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
367974
|
+
if (!isHtmlMime(expectedMimeType) && (isHtmlMime(mimeType) || looksLikeHtml(buffer2))) {
|
|
367975
|
+
throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, `Failed to download file bytes: Slack returned HTML instead of ${expectedMimeType ?? "media"}`);
|
|
367976
|
+
}
|
|
367771
367977
|
logger5.debug("File downloaded successfully", { size: buffer2.length, mimeType });
|
|
367772
367978
|
return { buffer: buffer2, mimeType };
|
|
367773
367979
|
} catch (error) {
|