@automagik/omni 2.260624.3 → 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 CHANGED
@@ -125004,7 +125004,7 @@ import { fileURLToPath } from "url";
125004
125004
  // package.json
125005
125005
  var package_default = {
125006
125006
  name: "@automagik/omni",
125007
- version: "2.260624.3",
125007
+ version: "2.260624.4",
125008
125008
  description: "LLM-optimized CLI for Omni",
125009
125009
  type: "module",
125010
125010
  bin: {
@@ -225291,7 +225291,7 @@ var init_sentry_scrub = __esm(() => {
225291
225291
  var require_package7 = __commonJS((exports, module) => {
225292
225292
  module.exports = {
225293
225293
  name: "@omni/api",
225294
- version: "2.260624.3",
225294
+ version: "2.260624.4",
225295
225295
  type: "module",
225296
225296
  exports: {
225297
225297
  ".": {
@@ -332957,6 +332957,62 @@ var init_batch_pricing = __esm(() => {
332957
332957
  // ../api/src/services/media-storage.ts
332958
332958
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
332959
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
+ }
332960
333016
  function getExtensionFromMime2(mimeType) {
332961
333017
  const mimeToExt = {
332962
333018
  "image/jpeg": ".jpg",
@@ -333045,12 +333101,16 @@ class MediaStorageService {
333045
333101
  };
333046
333102
  }
333047
333103
  async storeFromUrl(instanceId, messageId, url, mimeType, timestamp3, fetchOptions) {
333048
- const response = await fetch(url, fetchOptions);
333104
+ const response = await fetchWithOptionalAuthenticatedRedirects(url, fetchOptions);
333049
333105
  if (!response.ok) {
333050
333106
  throw new Error(`Failed to download media: ${response.status}`);
333051
333107
  }
333108
+ const responseContentType = response.headers.get("content-type") ?? undefined;
333052
333109
  const buffer3 = Buffer.from(await response.arrayBuffer());
333053
- const contentType = mimeType ?? response.headers.get("content-type") ?? undefined;
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;
333054
333114
  return this.storeFromBuffer(instanceId, messageId, buffer3, contentType, timestamp3);
333055
333115
  }
333056
333116
  async updateMessageLocalPath(messageId, localPath) {
@@ -356791,6 +356851,27 @@ async function resolveMessageFromRef(services, ref) {
356791
356851
  if ("messageId" in ref) {
356792
356852
  return services.messages.getById(ref.messageId);
356793
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
+ }
356794
356875
  const found = await services.messages.getByExternalId(ref.chatId, ref.externalId);
356795
356876
  if (!found) {
356796
356877
  throw new OmniError({
@@ -356802,6 +356883,17 @@ async function resolveMessageFromRef(services, ref) {
356802
356883
  }
356803
356884
  return found;
356804
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
+ }
356805
356897
  async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRowId) {
356806
356898
  const cfg = resolveCloseContactConfig(outcome, null);
356807
356899
  if (isHardTerminalOutcome(outcome)) {
@@ -357089,7 +357181,8 @@ var init_messages5 = __esm(() => {
357089
357181
  });
357090
357182
  messageRefSchema = exports_external.union([
357091
357183
  exports_external.object({ messageId: exports_external.string().uuid() }),
357092
- 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) })
357093
357186
  ]);
357094
357187
  messagesRoutes.post("/media/download", zValidator("json", messageRefSchema), async (c) => {
357095
357188
  const body = c.req.valid("json");
@@ -357107,6 +357200,7 @@ var init_messages5 = __esm(() => {
357107
357200
  recoverable: false
357108
357201
  });
357109
357202
  }
357203
+ const instance4 = await services.instances.getById(instanceId);
357110
357204
  checkInstanceAccess2(apiKey, instanceId);
357111
357205
  if (!message2.hasMedia || !message2.mediaUrl) {
357112
357206
  return c.json({
@@ -357134,7 +357228,7 @@ var init_messages5 = __esm(() => {
357134
357228
  }
357135
357229
  if (!cached) {
357136
357230
  try {
357137
- 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));
357138
357232
  mediaLocalPath = result.localPath;
357139
357233
  await mediaStorage2.updateMessageLocalPath(message2.id, result.localPath);
357140
357234
  mediaDownloadLog.info("Downloaded and cached media", {
@@ -357155,7 +357249,7 @@ var init_messages5 = __esm(() => {
357155
357249
  });
357156
357250
  }
357157
357251
  }
357158
- const downloadUrl = `/api/v2/media/${instanceId}/${mediaLocalPath}`;
357252
+ const downloadUrl = `/api/v2/media/${mediaLocalPath}`;
357159
357253
  return c.json({
357160
357254
  data: {
357161
357255
  messageId: message2.id,
@@ -367806,6 +367900,51 @@ function setupCommandHandlers(app, instanceId, commandNames, callbacks, logger5)
367806
367900
  init_src2();
367807
367901
  init_types10();
367808
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
+ }
367809
367948
  function extractFileInfo(files) {
367810
367949
  if (!files || !Array.isArray(files))
367811
367950
  return [];
@@ -367822,20 +367961,19 @@ function extractFileInfo(files) {
367822
367961
  };
367823
367962
  });
367824
367963
  }
367825
- async function downloadSlackFile(url, botToken, logger5) {
367964
+ async function downloadSlackFile(url, botToken, logger5, expectedMimeType) {
367826
367965
  logger5.debug("Downloading file from Slack CDN", { url: url.substring(0, 50) });
367827
367966
  try {
367828
- const response = await fetch(url, {
367829
- headers: {
367830
- Authorization: `Bearer ${botToken}`
367831
- }
367832
- });
367967
+ const response = await fetchSlackPrivateUrl(url, botToken);
367833
367968
  if (!response.ok) {
367834
367969
  throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, `Failed to download file: HTTP ${response.status} ${response.statusText}`);
367835
367970
  }
367836
367971
  downloadGuard2.checkResponse(response, logger5, { channel: "slack", url: url.substring(0, 50) });
367837
367972
  const buffer2 = Buffer.from(await response.arrayBuffer());
367838
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
+ }
367839
367977
  logger5.debug("File downloaded successfully", { size: buffer2.length, mimeType });
367840
367978
  return { buffer: buffer2, mimeType };
367841
367979
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260624.3",
3
+ "version": "2.260624.4",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {