@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-N5CKISI3.mjs";
5
+ } from "./chunk-VD5M6QAI.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);
@@ -20269,6 +20296,28 @@ var BatchFailureError = class extends Error {
20269
20296
  this.terminal = terminal;
20270
20297
  }
20271
20298
  };
20299
+ var FlaggedBatchError = class extends Error {
20300
+ };
20301
+ function extracted(result) {
20302
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
20303
+ }
20304
+ function batchError(result) {
20305
+ const failure = result.failure;
20306
+ if (failure?.reason === "http-terminal") {
20307
+ const status = failure.httpStatus;
20308
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
20309
+ return new BatchFailureError(
20310
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
20311
+ true
20312
+ );
20313
+ return new AccountFailureError(
20314
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
20315
+ );
20316
+ }
20317
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
20318
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
20319
+ return new Error("Extraction batch incomplete; retained for retry");
20320
+ }
20272
20321
  var AccountFailureError = class extends Error {
20273
20322
  };
20274
20323
  var LockedSourcesError = class extends Error {
@@ -20277,6 +20326,16 @@ var LockedSourcesError = class extends Error {
20277
20326
  this.persistentIds = persistentIds;
20278
20327
  }
20279
20328
  };
20329
+ function splitTurns(messages) {
20330
+ const turns = [];
20331
+ for (const message of messages) {
20332
+ const last = turns[turns.length - 1];
20333
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
20334
+ turns.push([message]);
20335
+ else last.push(message);
20336
+ }
20337
+ return turns;
20338
+ }
20280
20339
  function idsOf(job) {
20281
20340
  return JSON.parse(String(job._getRaw("message_ids")));
20282
20341
  }
@@ -20427,6 +20486,10 @@ function createDurableAutoExtractor(options) {
20427
20486
  skippedThisSession.add(job.id);
20428
20487
  return false;
20429
20488
  }
20489
+ if (error instanceof FlaggedBatchError) {
20490
+ await acknowledge(job, ids, loaded, true);
20491
+ return true;
20492
+ }
20430
20493
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
20431
20494
  if (!counted) return false;
20432
20495
  const prior = sessionFailures.get(job.id);
@@ -20553,37 +20616,50 @@ function createDurableAutoExtractor(options) {
20553
20616
  const messages = loaded.flatMap(
20554
20617
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
20555
20618
  );
20556
- let success = messages.length === 0;
20557
- let result;
20558
- if (messages.length) {
20559
- result = await extractBatch(
20560
- messages,
20619
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
20620
+ const jobFolderId = job._getRaw("folder_id") ?? null;
20621
+ const runExtraction = async (batch, batchIds) => {
20622
+ const result = await extractBatch(
20623
+ batch,
20561
20624
  conversationId,
20562
- ids,
20563
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
20564
- job._getRaw("folder_id") ?? null
20625
+ batchIds,
20626
+ jobScope,
20627
+ jobFolderId
20565
20628
  );
20566
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
20567
20629
  options.onTurnComplete?.(result);
20568
- }
20569
- if (!success) {
20570
- const failure = result?.failure;
20571
- if (failure?.reason === "http-terminal") {
20572
- const status = failure.httpStatus;
20573
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
20574
- throw new BatchFailureError(
20575
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
20576
- true
20630
+ return result;
20631
+ };
20632
+ const flaggedIds = /* @__PURE__ */ new Set();
20633
+ if (messages.length) {
20634
+ const result = await runExtraction(messages, ids);
20635
+ if (result.failure?.reason === "content-flagged") {
20636
+ const turns = splitTurns(messages);
20637
+ if (turns.length < 2)
20638
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
20639
+ for (const turn of turns) {
20640
+ const turnResult = await runExtraction(
20641
+ turn,
20642
+ turn.map((m) => m.id)
20577
20643
  );
20578
- throw new AccountFailureError(
20579
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
20580
- );
20581
- }
20582
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
20583
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
20584
- throw new Error("Extraction batch incomplete; retained for retry");
20644
+ if (turnResult.failure?.reason === "content-flagged")
20645
+ for (const m of turn) flaggedIds.add(m.id);
20646
+ else if (!extracted(turnResult)) throw batchError(turnResult);
20647
+ }
20648
+ if (flaggedIds.size)
20649
+ reportError(
20650
+ new Error(
20651
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
20652
+ ),
20653
+ conversationId
20654
+ );
20655
+ } else if (!extracted(result)) throw batchError(result);
20585
20656
  }
20586
- await acknowledge(job, ids, loaded, false);
20657
+ await acknowledge(
20658
+ job,
20659
+ ids,
20660
+ loaded,
20661
+ ids.slice(-2).some((id) => flaggedIds.has(id))
20662
+ );
20587
20663
  scheduleAfterSuccess = true;
20588
20664
  } catch (error) {
20589
20665
  attempts.set(job.id, attempted + 1);
@@ -22241,10 +22317,10 @@ function useChatStorage(options) {
22241
22317
  let mergedTools2 = void 0;
22242
22318
  let filteredServerTools2 = [];
22243
22319
  const isServerToolsFunction2 = typeof serverToolsFilter === "function";
22244
- const extracted2 = extractUserMessageFromMessages(messages);
22320
+ const extracted3 = extractUserMessageFromMessages(messages);
22245
22321
  const messageContent = resolveStoredUserContent(
22246
22322
  storedUserContent,
22247
- extracted2?.content ?? ""
22323
+ extracted3?.content ?? ""
22248
22324
  );
22249
22325
  let skipUserEmbedding;
22250
22326
  let skipEmbeddingFailed = false;
@@ -22410,15 +22486,15 @@ function useChatStorage(options) {
22410
22486
  skipped: true
22411
22487
  };
22412
22488
  }
22413
- const extracted = extractUserMessageFromMessages(messages);
22414
- if (!extracted || !extracted.content && !extracted.files?.length) {
22489
+ const extracted2 = extractUserMessageFromMessages(messages);
22490
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
22415
22491
  return {
22416
22492
  data: null,
22417
22493
  error: "No user message found in messages array"
22418
22494
  };
22419
22495
  }
22420
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
22421
- const filesForStorage = files ?? extracted.files;
22496
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
22497
+ const filesForStorage = files ?? extracted2.files;
22422
22498
  let convId;
22423
22499
  try {
22424
22500
  convId = await ensureConversation();
@@ -22746,9 +22822,9 @@ function useChatStorage(options) {
22746
22822
  if (result.error || !result.data) {
22747
22823
  const abortedResult = result;
22748
22824
  if (abortedResult.error === "Request aborted") {
22749
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
22750
- const assistantContent2 = extracted2.content;
22751
- const abortedThinkingContent = extracted2.thinking;
22825
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
22826
+ const assistantContent2 = extracted3.content;
22827
+ const abortedThinkingContent = extracted3.thinking;
22752
22828
  const responseModel = abortedResult.data?.model || model || "";
22753
22829
  let storedAssistantMessage2;
22754
22830
  try {
@@ -5202,7 +5202,12 @@ type PortalLlmFailureReason =
5202
5202
  /** Content parsed to a literal `null`, which is never a valid response. */
5203
5203
  | "null-completion"
5204
5204
  /** `totalTimeoutMs` was spent before the next attempt could run. */
5205
- | "time-budget-exhausted";
5205
+ | "time-budget-exhausted"
5206
+ /**
5207
+ * The portal's moderation gate refused the request. Terminal: the same input
5208
+ * is flagged again on every retry. See {@link isModerationResponse}.
5209
+ */
5210
+ | "content-flagged";
5206
5211
  /**
5207
5212
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
5208
5213
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -6439,7 +6444,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
6439
6444
  * Optional per-call request path override, forwarded to
6440
6445
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
6441
6446
  * `baseUrl + endpointOverride` instead of the default
6442
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
6447
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
6448
+ * model that needs the Responses API is moved from a `.../chat/completions`
6449
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
6443
6450
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
6444
6451
  * call time (see {@link validateEndpointOverride}).
6445
6452
  *
@@ -9121,7 +9128,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
9121
9128
  * Optional per-call request path override, forwarded to
9122
9129
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
9123
9130
  * `baseUrl + endpointOverride` instead of the default
9124
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
9131
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
9132
+ * model that needs the Responses API is moved from a `.../chat/completions`
9133
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
9125
9134
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
9126
9135
  * call time (see {@link validateEndpointOverride}).
9127
9136
  */
@@ -5202,7 +5202,12 @@ type PortalLlmFailureReason =
5202
5202
  /** Content parsed to a literal `null`, which is never a valid response. */
5203
5203
  | "null-completion"
5204
5204
  /** `totalTimeoutMs` was spent before the next attempt could run. */
5205
- | "time-budget-exhausted";
5205
+ | "time-budget-exhausted"
5206
+ /**
5207
+ * The portal's moderation gate refused the request. Terminal: the same input
5208
+ * is flagged again on every retry. See {@link isModerationResponse}.
5209
+ */
5210
+ | "content-flagged";
5206
5211
  /**
5207
5212
  * One attempt of a {@link callPortalJsonCompletion} call, reported to
5208
5213
  * `onAttempt` as it settles — success included. The wire-level diagnostic the
@@ -6439,7 +6444,9 @@ interface ExtractFactsOptions extends PortalLlmAuth {
6439
6444
  * Optional per-call request path override, forwarded to
6440
6445
  * {@link callPortalJsonCompletion}. When set, the extraction call POSTs to
6441
6446
  * `baseUrl + endpointOverride` instead of the default
6442
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
6447
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
6448
+ * model that needs the Responses API is moved from a `.../chat/completions`
6449
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
6443
6450
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
6444
6451
  * call time (see {@link validateEndpointOverride}).
6445
6452
  *
@@ -9121,7 +9128,9 @@ interface TopicExtractOptions extends PortalLlmAuth {
9121
9128
  * Optional per-call request path override, forwarded to
9122
9129
  * {@link callPortalJsonCompletion}. When set, topic extraction POSTs to
9123
9130
  * `baseUrl + endpointOverride` instead of the default
9124
- * `/api/v1/chat/completions` — path only, body unchanged. Lets callers route
9131
+ * `/api/v1/chat/completions`. The body follows the transport, not the path: a
9132
+ * model that needs the Responses API is moved from a `.../chat/completions`
9133
+ * override to its sibling `.../responses` with a Responses-shaped body. Lets callers route
9125
9134
  * this internal-utility pass to a dedicated endpoint. Invalid values throw at
9126
9135
  * call time (see {@link validateEndpointOverride}).
9127
9136
  */
@@ -19,7 +19,7 @@ import {
19
19
  taskTypeHeader,
20
20
  validateEndpointOverride,
21
21
  withInternalFlowMarker
22
- } from "./chunk-N5CKISI3.mjs";
22
+ } from "./chunk-VD5M6QAI.mjs";
23
23
  import {
24
24
  BASE_URL,
25
25
  MCP_R2_DOMAIN,
@@ -14952,7 +14952,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
14952
14952
  content: m.content,
14953
14953
  similarity: m.similarity
14954
14954
  }));
14955
- const { consolidateMemory: doConsolidate } = await import("./consolidate-N56RXPCW.mjs");
14955
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-6J5HHAJY.mjs");
14956
14956
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
14957
14957
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
14958
14958
  if (decision.action === "supersede" && decision.content) {
@@ -18599,6 +18599,28 @@ var BatchFailureError = class extends Error {
18599
18599
  this.terminal = terminal;
18600
18600
  }
18601
18601
  };
18602
+ var FlaggedBatchError = class extends Error {
18603
+ };
18604
+ function extracted(result) {
18605
+ return result.failedCount === 0 && result.outcome !== "empty-after-retry";
18606
+ }
18607
+ function batchError(result) {
18608
+ const failure = result.failure;
18609
+ if (failure?.reason === "http-terminal") {
18610
+ const status = failure.httpStatus;
18611
+ if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
18612
+ return new BatchFailureError(
18613
+ `Extraction rejected the batch (HTTP ${status}); not retried this session`,
18614
+ true
18615
+ );
18616
+ return new AccountFailureError(
18617
+ `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
18618
+ );
18619
+ }
18620
+ if (result.failedCount > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
18621
+ return new BatchFailureError("Extraction batch incomplete; retained for retry", false);
18622
+ return new Error("Extraction batch incomplete; retained for retry");
18623
+ }
18602
18624
  var AccountFailureError = class extends Error {
18603
18625
  };
18604
18626
  var LockedSourcesError = class extends Error {
@@ -18607,6 +18629,16 @@ var LockedSourcesError = class extends Error {
18607
18629
  this.persistentIds = persistentIds;
18608
18630
  }
18609
18631
  };
18632
+ function splitTurns(messages) {
18633
+ const turns = [];
18634
+ for (const message of messages) {
18635
+ const last = turns[turns.length - 1];
18636
+ if (!last || message.role === "user" && last.some((m) => m.role === "user"))
18637
+ turns.push([message]);
18638
+ else last.push(message);
18639
+ }
18640
+ return turns;
18641
+ }
18610
18642
  function idsOf(job) {
18611
18643
  return JSON.parse(String(job._getRaw("message_ids")));
18612
18644
  }
@@ -18757,6 +18789,10 @@ function createDurableAutoExtractor(options) {
18757
18789
  skippedThisSession.add(job.id);
18758
18790
  return false;
18759
18791
  }
18792
+ if (error instanceof FlaggedBatchError) {
18793
+ await acknowledge(job, ids, loaded, true);
18794
+ return true;
18795
+ }
18760
18796
  const counted = error instanceof BatchFailureError || error instanceof LockedSourcesError && error.persistentIds.length > 0;
18761
18797
  if (!counted) return false;
18762
18798
  const prior = sessionFailures.get(job.id);
@@ -18883,37 +18919,50 @@ function createDurableAutoExtractor(options) {
18883
18919
  const messages = loaded.flatMap(
18884
18920
  (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
18885
18921
  );
18886
- let success = messages.length === 0;
18887
- let result;
18888
- if (messages.length) {
18889
- result = await extractBatch(
18890
- messages,
18922
+ const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
18923
+ const jobFolderId = job._getRaw("folder_id") ?? null;
18924
+ const runExtraction = async (batch, batchIds) => {
18925
+ const result = await extractBatch(
18926
+ batch,
18891
18927
  conversationId,
18892
- ids,
18893
- currentScope() === "private" ? "private" : String(job._getRaw("scope")),
18894
- job._getRaw("folder_id") ?? null
18928
+ batchIds,
18929
+ jobScope,
18930
+ jobFolderId
18895
18931
  );
18896
- success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
18897
18932
  options.onTurnComplete?.(result);
18898
- }
18899
- if (!success) {
18900
- const failure = result?.failure;
18901
- if (failure?.reason === "http-terminal") {
18902
- const status = failure.httpStatus;
18903
- if (status !== void 0 && REQUEST_REJECTED_STATUSES.has(status))
18904
- throw new BatchFailureError(
18905
- `Extraction rejected the batch (HTTP ${status}); not retried this session`,
18906
- true
18933
+ return result;
18934
+ };
18935
+ const flaggedIds = /* @__PURE__ */ new Set();
18936
+ if (messages.length) {
18937
+ const result = await runExtraction(messages, ids);
18938
+ if (result.failure?.reason === "content-flagged") {
18939
+ const turns = splitTurns(messages);
18940
+ if (turns.length < 2)
18941
+ throw new FlaggedBatchError("Extraction batch refused by moderation; abandoned");
18942
+ for (const turn of turns) {
18943
+ const turnResult = await runExtraction(
18944
+ turn,
18945
+ turn.map((m) => m.id)
18907
18946
  );
18908
- throw new AccountFailureError(
18909
- `Extraction refused for the account (HTTP ${status ?? "unknown"}); retried next session`
18910
- );
18911
- }
18912
- if ((result?.failedCount ?? 0) > 0 || failure !== void 0 && CONTENT_FAILURE_REASONS.has(failure.reason))
18913
- throw new BatchFailureError("Extraction batch incomplete; retained for retry", false);
18914
- throw new Error("Extraction batch incomplete; retained for retry");
18947
+ if (turnResult.failure?.reason === "content-flagged")
18948
+ for (const m of turn) flaggedIds.add(m.id);
18949
+ else if (!extracted(turnResult)) throw batchError(turnResult);
18950
+ }
18951
+ if (flaggedIds.size)
18952
+ reportError(
18953
+ new Error(
18954
+ `Dropped ${flaggedIds.size} source id(s) of an extraction batch refused by moderation`
18955
+ ),
18956
+ conversationId
18957
+ );
18958
+ } else if (!extracted(result)) throw batchError(result);
18915
18959
  }
18916
- await acknowledge(job, ids, loaded, false);
18960
+ await acknowledge(
18961
+ job,
18962
+ ids,
18963
+ loaded,
18964
+ ids.slice(-2).some((id) => flaggedIds.has(id))
18965
+ );
18917
18966
  scheduleAfterSuccess = true;
18918
18967
  } catch (error) {
18919
18968
  attempts.set(job.id, attempted + 1);
@@ -20568,10 +20617,10 @@ function useChatStorage(options) {
20568
20617
  let mergedTools2 = void 0;
20569
20618
  let filteredServerTools2 = [];
20570
20619
  const isServerToolsFunction2 = typeof serverToolsFilter === "function";
20571
- const extracted2 = extractUserMessageFromMessages(messages);
20620
+ const extracted3 = extractUserMessageFromMessages(messages);
20572
20621
  const messageContent = resolveStoredUserContent(
20573
20622
  storedUserContent,
20574
- extracted2?.content ?? ""
20623
+ extracted3?.content ?? ""
20575
20624
  );
20576
20625
  let skipUserEmbedding;
20577
20626
  let skipEmbeddingFailed = false;
@@ -20737,15 +20786,15 @@ function useChatStorage(options) {
20737
20786
  skipped: true
20738
20787
  };
20739
20788
  }
20740
- const extracted = extractUserMessageFromMessages(messages);
20741
- if (!extracted || !extracted.content && !extracted.files?.length) {
20789
+ const extracted2 = extractUserMessageFromMessages(messages);
20790
+ if (!extracted2 || !extracted2.content && !extracted2.files?.length) {
20742
20791
  return {
20743
20792
  data: null,
20744
20793
  error: "No user message found in messages array"
20745
20794
  };
20746
20795
  }
20747
- const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
20748
- const filesForStorage = files ?? extracted.files;
20796
+ const contentForStorage = resolveStoredUserContent(storedUserContent, extracted2.content);
20797
+ const filesForStorage = files ?? extracted2.files;
20749
20798
  let convId;
20750
20799
  try {
20751
20800
  convId = await ensureConversation();
@@ -21073,9 +21122,9 @@ function useChatStorage(options) {
21073
21122
  if (result.error || !result.data) {
21074
21123
  const abortedResult = result;
21075
21124
  if (abortedResult.error === "Request aborted") {
21076
- const extracted2 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
21077
- const assistantContent2 = extracted2.content;
21078
- const abortedThinkingContent = extracted2.thinking;
21125
+ const extracted3 = abortedResult.data ? extractAssistantText(abortedResult.data) : { content: "", thinking: void 0 };
21126
+ const assistantContent2 = extracted3.content;
21127
+ const abortedThinkingContent = extracted3.thinking;
21079
21128
  const responseModel = abortedResult.data?.model || model || "";
21080
21129
  let storedAssistantMessage2;
21081
21130
  try {