@anuma/sdk 6.30.1 → 6.30.2

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.
@@ -726,13 +726,21 @@ function responsesExtra(extra) {
726
726
  }
727
727
  return out;
728
728
  }
729
+ function splitEndpoint(endpoint) {
730
+ const cut = endpoint.search(/[?#]/);
731
+ const rawPath = cut === -1 ? endpoint : endpoint.slice(0, cut);
732
+ return {
733
+ path: rawPath.replace(/\/+$/, ""),
734
+ rest: cut === -1 ? "" : endpoint.slice(cut)
735
+ };
736
+ }
729
737
  var FOREIGN_ENDPOINT_SUFFIX = {
730
738
  chat: "/responses",
731
739
  responses: "/chat/completions"
732
740
  };
733
741
  function assertTransportMatchesEndpoint(transport, endpoint) {
734
742
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
735
- if (!endpoint.endsWith(foreign)) return;
743
+ if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
736
744
  throw new Error(
737
745
  `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
738
746
  );
@@ -778,14 +786,20 @@ async function resolvePortalAuthHeaders(auth, tag) {
778
786
  }
779
787
  async function callPortalJsonCompletion(req) {
780
788
  const log = getLogger();
781
- let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
789
+ const transport = req.transport ?? (requiresResponsesTransport(req.model) ? "responses" : "chat");
790
+ const autoUpgraded = req.transport === void 0 && transport === "responses";
791
+ let endpoint = transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
782
792
  if (req.endpointOverride !== void 0) {
783
793
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
784
794
  if (!overrideValidation.valid) {
785
795
  throw new Error(overrideValidation.message);
786
796
  }
787
797
  endpoint = overrideValidation.endpoint;
788
- assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
798
+ const { path, rest } = splitEndpoint(endpoint);
799
+ if (autoUpgraded && path.endsWith("/chat/completions")) {
800
+ endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
801
+ }
802
+ assertTransportMatchesEndpoint(transport, endpoint);
789
803
  }
790
804
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
791
805
  const startedAt = Date.now();
@@ -810,7 +824,10 @@ async function callPortalJsonCompletion(req) {
810
824
  ),
811
825
  ...reinforce && { reinforceJsonContract: true }
812
826
  } : reinforce ? { ...req, reinforceJsonContract: true } : req;
813
- const outcome = await attemptPortalJson(attemptReq, endpoint);
827
+ const outcome = await attemptPortalJson(
828
+ { ...attemptReq, transport },
829
+ endpoint
830
+ );
814
831
  if (outcome.kind === "ok") {
815
832
  req.onAttempt?.({ attempt, ok: true });
816
833
  return outcome.value;
@@ -962,6 +979,13 @@ async function attemptPortalJson(req, endpoint) {
962
979
  }
963
980
  clearTimeout(timer);
964
981
  logPortalUsage(body, req.tag);
982
+ if (isModerationResponse(body)) {
983
+ return {
984
+ kind: "terminal",
985
+ code: "content-flagged",
986
+ reason: "portal moderation refused the request"
987
+ };
988
+ }
965
989
  const rawContent = extractCompletionContent(body);
966
990
  if (!rawContent) {
967
991
  return {
@@ -1047,6 +1071,9 @@ function logPortalUsage(body, tag) {
1047
1071
  } catch {
1048
1072
  }
1049
1073
  }
1074
+ function isModerationResponse(body) {
1075
+ return typeof body === "object" && body !== null && body.id === "moderation";
1076
+ }
1050
1077
  function extractCompletionContent(body) {
1051
1078
  if (typeof body !== "object" || body === null) return null;
1052
1079
  const responsesText = extractResponsesContent(body);
@@ -2,7 +2,7 @@ import {
2
2
  callPortalJsonCompletion,
3
3
  notifyConsolidationFallback,
4
4
  resolvePiiRedactor
5
- } from "./chunk-LBIKGBXE.mjs";
5
+ } from "./chunk-NLTO6Q36.mjs";
6
6
  import "./chunk-GYKJOWEO.mjs";
7
7
 
8
8
  // src/lib/memory/consolidate.ts
@@ -828,9 +828,17 @@ function responsesExtra(extra) {
828
828
  }
829
829
  return out;
830
830
  }
831
+ function splitEndpoint(endpoint) {
832
+ const cut = endpoint.search(/[?#]/);
833
+ const rawPath = cut === -1 ? endpoint : endpoint.slice(0, cut);
834
+ return {
835
+ path: rawPath.replace(/\/+$/, ""),
836
+ rest: cut === -1 ? "" : endpoint.slice(cut)
837
+ };
838
+ }
831
839
  function assertTransportMatchesEndpoint(transport, endpoint) {
832
840
  const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
833
- if (!endpoint.endsWith(foreign)) return;
841
+ if (!splitEndpoint(endpoint).path.endsWith(foreign)) return;
834
842
  throw new Error(
835
843
  `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
836
844
  );
@@ -875,14 +883,20 @@ async function resolvePortalAuthHeaders(auth, tag) {
875
883
  }
876
884
  async function callPortalJsonCompletion(req) {
877
885
  const log = getLogger();
878
- let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
886
+ const transport = req.transport ?? (requiresResponsesTransport(req.model) ? "responses" : "chat");
887
+ const autoUpgraded = req.transport === void 0 && transport === "responses";
888
+ let endpoint = transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
879
889
  if (req.endpointOverride !== void 0) {
880
890
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
881
891
  if (!overrideValidation.valid) {
882
892
  throw new Error(overrideValidation.message);
883
893
  }
884
894
  endpoint = overrideValidation.endpoint;
885
- assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
895
+ const { path, rest } = splitEndpoint(endpoint);
896
+ if (autoUpgraded && path.endsWith("/chat/completions")) {
897
+ endpoint = `${path.slice(0, -"/chat/completions".length)}/responses${rest}`;
898
+ }
899
+ assertTransportMatchesEndpoint(transport, endpoint);
886
900
  }
887
901
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
888
902
  const startedAt = Date.now();
@@ -907,7 +921,10 @@ async function callPortalJsonCompletion(req) {
907
921
  ),
908
922
  ...reinforce && { reinforceJsonContract: true }
909
923
  } : reinforce ? { ...req, reinforceJsonContract: true } : req;
910
- const outcome = await attemptPortalJson(attemptReq, endpoint);
924
+ const outcome = await attemptPortalJson(
925
+ { ...attemptReq, transport },
926
+ endpoint
927
+ );
911
928
  if (outcome.kind === "ok") {
912
929
  req.onAttempt?.({ attempt, ok: true });
913
930
  return outcome.value;
@@ -1059,6 +1076,13 @@ async function attemptPortalJson(req, endpoint) {
1059
1076
  }
1060
1077
  clearTimeout(timer);
1061
1078
  logPortalUsage(body, req.tag);
1079
+ if (isModerationResponse(body)) {
1080
+ return {
1081
+ kind: "terminal",
1082
+ code: "content-flagged",
1083
+ reason: "portal moderation refused the request"
1084
+ };
1085
+ }
1062
1086
  const rawContent = extractCompletionContent(body);
1063
1087
  if (!rawContent) {
1064
1088
  return {
@@ -1144,6 +1168,9 @@ function logPortalUsage(body, tag) {
1144
1168
  } catch {
1145
1169
  }
1146
1170
  }
1171
+ function isModerationResponse(body) {
1172
+ return typeof body === "object" && body !== null && body.id === "moderation";
1173
+ }
1147
1174
  function extractCompletionContent(body) {
1148
1175
  if (typeof body !== "object" || body === null) return null;
1149
1176
  const responsesText = extractResponsesContent(body);
@@ -21616,6 +21643,28 @@ var BatchFailureError = class extends Error {
21616
21643
  this.terminal = terminal;
21617
21644
  }
21618
21645
  };
21646
+ var FlaggedBatchError = class extends Error {
21647
+ };
21648
+ function extracted(result) {
21649
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
21650
+ }
21651
+ function batchError(result) {
21652
+ const failure = result.failure;
21653
+ if (failure?.reason === "http-terminal") {
21654
+ const status = failure.httpStatus;
21655
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
21656
+ return new BatchFailureError(
21657
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
21658
+ true
21659
+ );
21660
+ return new AccountFailureError(
21661
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
21662
+ );
21663
+ }
21664
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
21665
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
21666
+ return new Error("Extraction batch incomplete; retained for retry");
21667
+ }
21619
21668
  var AccountFailureError = class extends Error {
21620
21669
  };
21621
21670
  var LockedSourcesError = class extends Error {
@@ -21624,6 +21673,16 @@ var LockedSourcesError = class extends Error {
21624
21673
  this.persistentIds = persistentIds;
21625
21674
  }
21626
21675
  };
21676
+ function splitTurns(messages) {
21677
+ const turns = [];
21678
+ for (const message of messages) {
21679
+ const last = turns[turns.length - 1];
21680
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
21681
+ turns.push([message]);
21682
+ else last.push(message);
21683
+ }
21684
+ return turns;
21685
+ }
21627
21686
  function idsOf(job) {
21628
21687
  return JSON.parse(String(job._getRaw("message_ids")));
21629
21688
  }
@@ -21774,6 +21833,10 @@ function createDurableAutoExtractor(options) {
21774
21833
  skippedThisSession.add(job.id);
21775
21834
  return false;
21776
21835
  }
21836
+ if (error instanceof FlaggedBatchError) {
21837
+ await acknowledge(job, ids, loaded, true);
21838
+ return true;
21839
+ }
21777
21840
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
21778
21841
  if (!counted) return false;
21779
21842
  const prior = sessionFailures.get(job.id);
@@ -21900,37 +21963,50 @@ function createDurableAutoExtractor(options) {
21900
21963
  const messages = loaded.flatMap(
21901
21964
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
21902
21965
  );
21903
- let success = messages.length === 0;
21904
- let result;
21905
- if (messages.length) {
21906
- result = await extractBatch(
21907
- messages,
21966
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
21967
+ const jobFolderId = job._getRaw("folder_id") ?? null;
21968
+ const runExtraction = async (batch, batchIds) => {
21969
+ const result = await extractBatch(
21970
+ batch,
21908
21971
  conversationId,
21909
- ids,
21910
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
21911
- job._getRaw("folder_id") ?? null
21972
+ batchIds,
21973
+ jobScope,
21974
+ jobFolderId
21912
21975
  );
21913
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
21914
21976
  options.onTurnComplete?.(result);
21915
- }
21916
- if (!success) {
21917
- const failure = result?.failure;
21918
- if (failure?.reason === "http-terminal") {
21919
- const status = failure.httpStatus;
21920
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
21921
- throw new BatchFailureError(
21922
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
21923
- true
21977
+ return result;
21978
+ };
21979
+ const flaggedIds = /* @__PURE__ */ new Set();
21980
+ if (messages.length) {
21981
+ const result = await runExtraction(messages, ids);
21982
+ if (result.failure?.reason === "content-flagged") {
21983
+ const turns = splitTurns(messages);
21984
+ if (turns.length < 2)
21985
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
21986
+ for (const turn of turns) {
21987
+ const turnResult = await runExtraction(
21988
+ turn,
21989
+ turn.map((m) => m.id)
21924
21990
  );
21925
- throw new AccountFailureError(
21926
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
21927
- );
21928
- }
21929
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
21930
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
21931
- throw new Error("Extraction batch incomplete; retained for retry");
21991
+ if (turnResult.failure?.reason === "content-flagged")
21992
+ for (const m of turn) flaggedIds.add(m.id);
21993
+ else if (!extracted(turnResult)) throw batchError(turnResult);
21994
+ }
21995
+ if (flaggedIds.size)
21996
+ reportError(
21997
+ new Error(
21998
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
21999
+ ),
22000
+ conversationId
22001
+ );
22002
+ } else if (!extracted(result)) throw batchError(result);
21932
22003
  }
21933
- await acknowledge(job, ids, loaded, false);
22004
+ await acknowledge(
22005
+ job,
22006
+ ids,
22007
+ loaded,
22008
+ ids.slice(-2).some((id) => flaggedIds.has(id))
22009
+ );
21934
22010
  scheduleAfterSuccess = true;
21935
22011
  } catch (error) {
21936
22012
  attempts.set(job.id, attempted + 1);
@@ -25171,10 +25247,10 @@ function useChatStorage(options) {
25171
25247
  let skipStorageEmbeddings = null;
25172
25248
  let skipStorageEmbeddingsFailed = false;
25173
25249
  if (needsEmbeddings2 && getToken) {
25174
- const extracted2 = extractUserMessageFromMessages(messages);
25250
+ const extracted3 = extractUserMessageFromMessages(messages);
25175
25251
  const messageContent = resolveStoredUserContent(
25176
25252
  storedUserContent,
25177
- extracted2?.content ?? ""
25253
+ extracted3?.content ?? ""
25178
25254
  );
25179
25255
  if (messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
25180
25256
  const embeddingOptions = {
@@ -25331,15 +25407,15 @@ function useChatStorage(options) {
25331
25407
  skipped: true
25332
25408
  };
25333
25409
  }
25334
- const extracted = extractUserMessageFromMessages(messages);
25335
- if (!extracted || !extracted.content && !extracted.files?.length) {
25410
+ const extracted2 = extractUserMessageFromMessages(messages);
25411
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
25336
25412
  return {
25337
25413
  data: null,
25338
25414
  error: "No user message found in messages array"
25339
25415
  };
25340
25416
  }
25341
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
25342
- const filesForStorage = files ?? extracted.files;
25417
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
25418
+ const filesForStorage = files ?? extracted2.files;
25343
25419
  let fileContextForRequest;
25344
25420
  let fileContextIsCurrentTurn = false;
25345
25421
  let preprocessedFileIds = [];
@@ -25798,9 +25874,9 @@ function useChatStorage(options) {
25798
25874
  if (result.error || !result.data) {
25799
25875
  const abortedResult = result;
25800
25876
  if (abortedResult.error === "Request aborted") {
25801
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
25802
- const assistantContent2 = extracted2.content;
25803
- const abortedThinkingContent = extracted2.thinking;
25877
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
25878
+ const assistantContent2 = extracted3.content;
25879
+ const abortedThinkingContent = extracted3.thinking;
25804
25880
  const responseModel = abortedResult.data?.model || model || "";
25805
25881
  let storedAssistantMessage2;
25806
25882
  try {
@@ -6529,7 +6529,12 @@ type PortalLlmFailureReason =
6529
6529
  /** Content parsed to a literal `null`, which is never a valid response. */
6530
6530
  | "null-completion"
6531
6531
  /** `totalTimeoutMs` was spent before the next attempt could run. */
6532
- | "time-budget-exhausted";
6532
+ | "time-budget-exhausted"
6533
+ /**
6534
+ * The portal's moderation gate refused the request. Terminal: the same input
6535
+ * is flagged again on every retry. See {@link isModerationResponse}.
6536
+ */
6537
+ | "content-flagged";
6533
6538
  /**
6534
6539
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
6535
6540
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -7766,7 +7771,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
7766
7771
  * Optional per-call request path override, forwarded to
7767
7772
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
7768
7773
  * `baseUrl + endpointOverride` instead of the default
7769
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
7774
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
7775
+ * model that needs the Responses API is moved from a `.../chat/completions`
7776
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
7770
7777
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
7771
7778
  * call time (see {@link validateEndpointOverride}).
7772
7779
  *
@@ -10414,7 +10421,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
10414
10421
  * Optional per-call request path override, forwarded to
10415
10422
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
10416
10423
  * `baseUrl + endpointOverride` instead of the default
10417
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
10424
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
10425
+ * model that needs the Responses API is moved from a `.../chat/completions`
10426
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
10418
10427
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
10419
10428
  * call time (see {@link validateEndpointOverride}).
10420
10429
  */
@@ -6529,7 +6529,12 @@ type PortalLlmFailureReason =
6529
6529
  /** Content parsed to a literal `null`, which is never a valid response. */
6530
6530
  | "null-completion"
6531
6531
  /** `totalTimeoutMs` was spent before the next attempt could run. */
6532
- | "time-budget-exhausted";
6532
+ | "time-budget-exhausted"
6533
+ /**
6534
+ * The portal's moderation gate refused the request. Terminal: the same input
6535
+ * is flagged again on every retry. See {@link isModerationResponse}.
6536
+ */
6537
+ | "content-flagged";
6533
6538
  /**
6534
6539
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
6535
6540
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -7766,7 +7771,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
7766
7771
  * Optional per-call request path override, forwarded to
7767
7772
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
7768
7773
  * `baseUrl + endpointOverride` instead of the default
7769
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
7774
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
7775
+ * model that needs the Responses API is moved from a `.../chat/completions`
7776
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
7770
7777
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
7771
7778
  * call time (see {@link validateEndpointOverride}).
7772
7779
  *
@@ -10414,7 +10421,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
10414
10421
  * Optional per-call request path override, forwarded to
10415
10422
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
10416
10423
  * `baseUrl + endpointOverride` instead of the default
10417
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
10424
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
10425
+ * model that needs the Responses API is moved from a `.../chat/completions`
10426
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
10418
10427
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
10419
10428
  * call time (see {@link validateEndpointOverride}).
10420
10429
  */
@@ -18,7 +18,7 @@ import {
18
18
  taskTypeHeader,
19
19
  validateEndpointOverride,
20
20
  withInternalFlowMarker
21
- } from "./chunk-LBIKGBXE.mjs";
21
+ } from "./chunk-NLTO6Q36.mjs";
22
22
  import {
23
23
  BASE_URL,
24
24
  MCP_R2_DOMAIN,
@@ -16054,7 +16054,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
16054
16054
  content: m.content,
16055
16055
  similarity: m.similarity
16056
16056
  }));
16057
- const { consolidateMemory: doConsolidate } = await import("./consolidate-2NLZAEWR.mjs");
16057
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-MYS55OAB.mjs");
16058
16058
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
16059
16059
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
16060
16060
  if (decision.action === "supersede" && decision.content) {
@@ -19701,6 +19701,28 @@ var BatchFailureError = class extends Error {
19701
19701
  this.terminal = terminal;
19702
19702
  }
19703
19703
  };
19704
+ var FlaggedBatchError = class extends Error {
19705
+ };
19706
+ function extracted(result) {
19707
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
19708
+ }
19709
+ function batchError(result) {
19710
+ const failure = result.failure;
19711
+ if (failure?.reason === "http-terminal") {
19712
+ const status = failure.httpStatus;
19713
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
19714
+ return new BatchFailureError(
19715
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
19716
+ true
19717
+ );
19718
+ return new AccountFailureError(
19719
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
19720
+ );
19721
+ }
19722
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
19723
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
19724
+ return new Error("Extraction batch incomplete; retained for retry");
19725
+ }
19704
19726
  var AccountFailureError = class extends Error {
19705
19727
  };
19706
19728
  var LockedSourcesError = class extends Error {
@@ -19709,6 +19731,16 @@ var LockedSourcesError = class extends Error {
19709
19731
  this.persistentIds = persistentIds;
19710
19732
  }
19711
19733
  };
19734
+ function splitTurns(messages) {
19735
+ const turns = [];
19736
+ for (const message of messages) {
19737
+ const last = turns[turns.length - 1];
19738
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
19739
+ turns.push([message]);
19740
+ else last.push(message);
19741
+ }
19742
+ return turns;
19743
+ }
19712
19744
  function idsOf(job) {
19713
19745
  return JSON.parse(String(job._getRaw("message_ids")));
19714
19746
  }
@@ -19859,6 +19891,10 @@ function createDurableAutoExtractor(options) {
19859
19891
  skippedThisSession.add(job.id);
19860
19892
  return false;
19861
19893
  }
19894
+ if (error instanceof FlaggedBatchError) {
19895
+ await acknowledge(job, ids, loaded, true);
19896
+ return true;
19897
+ }
19862
19898
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
19863
19899
  if (!counted) return false;
19864
19900
  const prior = sessionFailures.get(job.id);
@@ -19985,37 +20021,50 @@ function createDurableAutoExtractor(options) {
19985
20021
  const messages = loaded.flatMap(
19986
20022
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
19987
20023
  );
19988
- let success = messages.length === 0;
19989
- let result;
19990
- if (messages.length) {
19991
- result = await extractBatch(
19992
- messages,
20024
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
20025
+ const jobFolderId = job._getRaw("folder_id") ?? null;
20026
+ const runExtraction = async (batch, batchIds) => {
20027
+ const result = await extractBatch(
20028
+ batch,
19993
20029
  conversationId,
19994
- ids,
19995
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
19996
- job._getRaw("folder_id") ?? null
20030
+ batchIds,
20031
+ jobScope,
20032
+ jobFolderId
19997
20033
  );
19998
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
19999
20034
  options.onTurnComplete?.(result);
20000
- }
20001
- if (!success) {
20002
- const failure = result?.failure;
20003
- if (failure?.reason === "http-terminal") {
20004
- const status = failure.httpStatus;
20005
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
20006
- throw new BatchFailureError(
20007
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
20008
- true
20035
+ return result;
20036
+ };
20037
+ const flaggedIds = /* @__PURE__ */ new Set();
20038
+ if (messages.length) {
20039
+ const result = await runExtraction(messages, ids);
20040
+ if (result.failure?.reason === "content-flagged") {
20041
+ const turns = splitTurns(messages);
20042
+ if (turns.length < 2)
20043
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
20044
+ for (const turn of turns) {
20045
+ const turnResult = await runExtraction(
20046
+ turn,
20047
+ turn.map((m) => m.id)
20009
20048
  );
20010
- throw new AccountFailureError(
20011
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
20012
- );
20013
- }
20014
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
20015
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
20016
- throw new Error("Extraction batch incomplete; retained for retry");
20049
+ if (turnResult.failure?.reason === "content-flagged")
20050
+ for (const m of turn) flaggedIds.add(m.id);
20051
+ else if (!extracted(turnResult)) throw batchError(turnResult);
20052
+ }
20053
+ if (flaggedIds.size)
20054
+ reportError(
20055
+ new Error(
20056
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
20057
+ ),
20058
+ conversationId
20059
+ );
20060
+ } else if (!extracted(result)) throw batchError(result);
20017
20061
  }
20018
- await acknowledge(job, ids, loaded, false);
20062
+ await acknowledge(
20063
+ job,
20064
+ ids,
20065
+ loaded,
20066
+ ids.slice(-2).some((id) => flaggedIds.has(id))
20067
+ );
20019
20068
  scheduleAfterSuccess = true;
20020
20069
  } catch (error) {
20021
20070
  attempts.set(job.id, attempted + 1);
@@ -23244,10 +23293,10 @@ function useChatStorage(options) {
23244
23293
  let skipStorageEmbeddings = null;
23245
23294
  let skipStorageEmbeddingsFailed = false;
23246
23295
  if (needsEmbeddings2 && getToken) {
23247
- const extracted2 = extractUserMessageFromMessages(messages);
23296
+ const extracted3 = extractUserMessageFromMessages(messages);
23248
23297
  const messageContent = resolveStoredUserContent(
23249
23298
  storedUserContent,
23250
- extracted2?.content ?? ""
23299
+ extracted3?.content ?? ""
23251
23300
  );
23252
23301
  if (messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
23253
23302
  const embeddingOptions = {
@@ -23404,15 +23453,15 @@ function useChatStorage(options) {
23404
23453
  skipped: true
23405
23454
  };
23406
23455
  }
23407
- const extracted = extractUserMessageFromMessages(messages);
23408
- if (!extracted || !extracted.content && !extracted.files?.length) {
23456
+ const extracted2 = extractUserMessageFromMessages(messages);
23457
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
23409
23458
  return {
23410
23459
  data: null,
23411
23460
  error: "No user message found in messages array"
23412
23461
  };
23413
23462
  }
23414
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
23415
- const filesForStorage = files ?? extracted.files;
23463
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
23464
+ const filesForStorage = files ?? extracted2.files;
23416
23465
  let fileContextForRequest;
23417
23466
  let fileContextIsCurrentTurn = false;
23418
23467
  let preprocessedFileIds = [];
@@ -23871,9 +23920,9 @@ function useChatStorage(options) {
23871
23920
  if (result.error || !result.data) {
23872
23921
  const abortedResult = result;
23873
23922
  if (abortedResult.error === "Request aborted") {
23874
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
23875
- const assistantContent2 = extracted2.content;
23876
- const abortedThinkingContent = extracted2.thinking;
23923
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
23924
+ const assistantContent2 = extracted3.content;
23925
+ const abortedThinkingContent = extracted3.thinking;
23877
23926
  const responseModel = abortedResult.data?.model || model || "";
23878
23927
  let storedAssistantMessage2;
23879
23928
  try {