@anuma/sdk 10.5.3 → 10.6.0

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.
@@ -6818,6 +6818,7 @@ function count(value) {
6818
6818
 
6819
6819
  // src/lib/chat/resumeStream.ts
6820
6820
  init_clientConfig();
6821
+ init_redactor();
6821
6822
 
6822
6823
  // src/lib/chat/toolLoop.ts
6823
6824
  init_clientConfig();
@@ -11164,16 +11165,34 @@ async function resumeStream(options) {
11164
11165
  onData,
11165
11166
  onThinking,
11166
11167
  onFinish,
11167
- onError
11168
+ onError,
11169
+ piiRedactor
11168
11170
  } = options;
11169
11171
  const strategy = getStrategy(handle.apiType);
11170
11172
  const accumulator = createStreamAccumulator(handle.model || void 0);
11173
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
11174
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
11171
11175
  const contentSmoother = new StreamSmoother((text16) => {
11172
- if (onData) onData(text16);
11176
+ if (contentDeAnon) contentDeAnon.push(text16);
11177
+ else if (onData) onData(text16);
11173
11178
  }, smoothing);
11174
11179
  const thinkingSmoother = new StreamSmoother((text16) => {
11175
- if (onThinking) onThinking(text16);
11180
+ if (thinkingDeAnon) thinkingDeAnon.push(text16);
11181
+ else if (onThinking) onThinking(text16);
11176
11182
  }, smoothing);
11183
+ const flushOutput = () => {
11184
+ contentSmoother.flush();
11185
+ thinkingSmoother.flush();
11186
+ contentDeAnon?.flush();
11187
+ thinkingDeAnon?.flush();
11188
+ };
11189
+ const buildFinal = () => strategy.buildFinalResponse(
11190
+ piiRedactor ? {
11191
+ ...accumulator,
11192
+ content: piiRedactor.deAnonymize(accumulator.content),
11193
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
11194
+ } : accumulator
11195
+ );
11177
11196
  const idleController = new AbortController();
11178
11197
  let watchdog;
11179
11198
  let idleFired = false;
@@ -11218,9 +11237,8 @@ async function resumeStream(options) {
11218
11237
  if (signal) signal.removeEventListener("abort", onCallerAbort);
11219
11238
  };
11220
11239
  const buildInterrupted = (message) => {
11221
- contentSmoother.flush();
11222
- thinkingSmoother.flush();
11223
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
11240
+ flushOutput();
11241
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
11224
11242
  return { data, error: message, interrupted: true };
11225
11243
  };
11226
11244
  if (combinedSignal.aborted) {
@@ -11268,18 +11286,16 @@ async function resumeStream(options) {
11268
11286
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
11269
11287
  const statusCode = parseSseStatusCode(err);
11270
11288
  if (statusCode === 410) {
11271
- contentSmoother.flush();
11272
- thinkingSmoother.flush();
11289
+ flushOutput();
11273
11290
  throw new StreamExpiredError(handle.inferenceId);
11274
11291
  }
11275
11292
  if (err.name === "AbortError" || combinedSignal.aborted) {
11276
11293
  return buildInterrupted(abortMessage());
11277
11294
  }
11278
- contentSmoother.flush();
11279
- thinkingSmoother.flush();
11295
+ flushOutput();
11280
11296
  if (onError) onError(err);
11281
11297
  return {
11282
- data: strategy.buildFinalResponse(accumulator),
11298
+ data: buildFinal(),
11283
11299
  error: err.message,
11284
11300
  interrupted: false,
11285
11301
  statusCode
@@ -11295,7 +11311,9 @@ async function resumeStream(options) {
11295
11311
  draining = true;
11296
11312
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
11297
11313
  if (playbackAborted) return buildInterrupted(abortMessage());
11298
- const response = strategy.buildFinalResponse(accumulator);
11314
+ contentDeAnon?.flush();
11315
+ thinkingDeAnon?.flush();
11316
+ const response = buildFinal();
11299
11317
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
11300
11318
  if (onFinish && deliveredOutput) onFinish(response);
11301
11319
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
@@ -12448,7 +12466,7 @@ async function createRemoteMemoryPersistence(options) {
12448
12466
  ...item,
12449
12467
  memory: {
12450
12468
  ...item.memory,
12451
- content: await decrypt(item.memory.content),
12469
+ content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
12452
12470
  ...item.memory.kind_value !== void 0 && {
12453
12471
  kind_value: await decrypt(item.memory.kind_value)
12454
12472
  }
@@ -12542,7 +12560,8 @@ async function createRemoteMemoryPersistence(options) {
12542
12560
  }
12543
12561
  if (existing && existing.version !== expectedVersion) throw conflict();
12544
12562
  const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
12545
- const content = (snapshot?.content ?? decoded?.memory.content) === memory.content ? snapshot?.encryptedContent ?? existing.memory.content : await encryptField2(memory.content);
12563
+ const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
12564
+ const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
12546
12565
  const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
12547
12566
  return {
12548
12567
  body: {
@@ -25859,6 +25878,13 @@ var METADATA_FIELDS = [
25859
25878
  "published_at",
25860
25879
  "geohash"
25861
25880
  ];
25881
+ var TOMBSTONE_FIELDS = [
25882
+ "scope",
25883
+ "is_deleted",
25884
+ "embedding_model",
25885
+ "topics_updated_at",
25886
+ "last_observed_at"
25887
+ ];
25862
25888
  function text15(value) {
25863
25889
  return typeof value === "string" && value !== "" ? value : void 0;
25864
25890
  }
@@ -26006,7 +26032,8 @@ async function embedMissing(prepared, options, model, signal) {
26006
26032
  }
26007
26033
  function sameMetadata2(local, remote, model) {
26008
26034
  const mine = metadata2(local, model);
26009
- return METADATA_FIELDS.every((field10) => {
26035
+ const fields = bool(local.is_deleted) ? TOMBSTONE_FIELDS : METADATA_FIELDS;
26036
+ return fields.every((field10) => {
26010
26037
  const a = mine[field10] ?? null;
26011
26038
  const b = remote[field10] ?? null;
26012
26039
  if (field10 === "embedding_model" && a === null) return true;
@@ -2753,6 +2753,8 @@ interface ResumeStreamOptions {
2753
2753
  onData?: (chunk: string) => void;
2754
2754
  /** Thinking/reasoning deltas as they replay. */
2755
2755
  onThinking?: (chunk: string) => void;
2756
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2757
+ piiRedactor?: PiiRedactor;
2756
2758
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2757
2759
  onFinish?: (response: ApiResponse) => void;
2758
2760
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -2753,6 +2753,8 @@ interface ResumeStreamOptions {
2753
2753
  onData?: (chunk: string) => void;
2754
2754
  /** Thinking/reasoning deltas as they replay. */
2755
2755
  onThinking?: (chunk: string) => void;
2756
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2757
+ piiRedactor?: PiiRedactor;
2756
2758
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2757
2759
  onFinish?: (response: ApiResponse) => void;
2758
2760
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -9497,16 +9497,34 @@ async function resumeStream(options) {
9497
9497
  onData,
9498
9498
  onThinking,
9499
9499
  onFinish,
9500
- onError
9500
+ onError,
9501
+ piiRedactor
9501
9502
  } = options;
9502
9503
  const strategy = getStrategy(handle.apiType);
9503
9504
  const accumulator = createStreamAccumulator(handle.model || void 0);
9505
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
9506
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
9504
9507
  const contentSmoother = new StreamSmoother((text16) => {
9505
- if (onData) onData(text16);
9508
+ if (contentDeAnon) contentDeAnon.push(text16);
9509
+ else if (onData) onData(text16);
9506
9510
  }, smoothing);
9507
9511
  const thinkingSmoother = new StreamSmoother((text16) => {
9508
- if (onThinking) onThinking(text16);
9512
+ if (thinkingDeAnon) thinkingDeAnon.push(text16);
9513
+ else if (onThinking) onThinking(text16);
9509
9514
  }, smoothing);
9515
+ const flushOutput = () => {
9516
+ contentSmoother.flush();
9517
+ thinkingSmoother.flush();
9518
+ contentDeAnon?.flush();
9519
+ thinkingDeAnon?.flush();
9520
+ };
9521
+ const buildFinal = () => strategy.buildFinalResponse(
9522
+ piiRedactor ? {
9523
+ ...accumulator,
9524
+ content: piiRedactor.deAnonymize(accumulator.content),
9525
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
9526
+ } : accumulator
9527
+ );
9510
9528
  const idleController = new AbortController();
9511
9529
  let watchdog;
9512
9530
  let idleFired = false;
@@ -9551,9 +9569,8 @@ async function resumeStream(options) {
9551
9569
  if (signal) signal.removeEventListener("abort", onCallerAbort);
9552
9570
  };
9553
9571
  const buildInterrupted = (message) => {
9554
- contentSmoother.flush();
9555
- thinkingSmoother.flush();
9556
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
9572
+ flushOutput();
9573
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
9557
9574
  return { data, error: message, interrupted: true };
9558
9575
  };
9559
9576
  if (combinedSignal.aborted) {
@@ -9601,18 +9618,16 @@ async function resumeStream(options) {
9601
9618
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
9602
9619
  const statusCode = parseSseStatusCode(err);
9603
9620
  if (statusCode === 410) {
9604
- contentSmoother.flush();
9605
- thinkingSmoother.flush();
9621
+ flushOutput();
9606
9622
  throw new StreamExpiredError(handle.inferenceId);
9607
9623
  }
9608
9624
  if (err.name === "AbortError" || combinedSignal.aborted) {
9609
9625
  return buildInterrupted(abortMessage());
9610
9626
  }
9611
- contentSmoother.flush();
9612
- thinkingSmoother.flush();
9627
+ flushOutput();
9613
9628
  if (onError) onError(err);
9614
9629
  return {
9615
- data: strategy.buildFinalResponse(accumulator),
9630
+ data: buildFinal(),
9616
9631
  error: err.message,
9617
9632
  interrupted: false,
9618
9633
  statusCode
@@ -9628,7 +9643,9 @@ async function resumeStream(options) {
9628
9643
  draining = true;
9629
9644
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
9630
9645
  if (playbackAborted) return buildInterrupted(abortMessage());
9631
- const response = strategy.buildFinalResponse(accumulator);
9646
+ contentDeAnon?.flush();
9647
+ thinkingDeAnon?.flush();
9648
+ const response = buildFinal();
9632
9649
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
9633
9650
  if (onFinish && deliveredOutput) onFinish(response);
9634
9651
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
@@ -10776,7 +10793,7 @@ async function createRemoteMemoryPersistence(options) {
10776
10793
  ...item,
10777
10794
  memory: {
10778
10795
  ...item.memory,
10779
- content: await decrypt(item.memory.content),
10796
+ content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
10780
10797
  ...item.memory.kind_value !== void 0 && {
10781
10798
  kind_value: await decrypt(item.memory.kind_value)
10782
10799
  }
@@ -10870,7 +10887,8 @@ async function createRemoteMemoryPersistence(options) {
10870
10887
  }
10871
10888
  if (existing && existing.version !== expectedVersion) throw conflict();
10872
10889
  const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
10873
- const content = (snapshot?.content ?? decoded?.memory.content) === memory.content ? snapshot?.encryptedContent ?? existing.memory.content : await encryptField2(memory.content);
10890
+ const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
10891
+ const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
10874
10892
  const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
10875
10893
  return {
10876
10894
  body: {
@@ -24140,6 +24158,13 @@ var METADATA_FIELDS = [
24140
24158
  "published_at",
24141
24159
  "geohash"
24142
24160
  ];
24161
+ var TOMBSTONE_FIELDS = [
24162
+ "scope",
24163
+ "is_deleted",
24164
+ "embedding_model",
24165
+ "topics_updated_at",
24166
+ "last_observed_at"
24167
+ ];
24143
24168
  function text15(value) {
24144
24169
  return typeof value === "string" && value !== "" ? value : void 0;
24145
24170
  }
@@ -24287,7 +24312,8 @@ async function embedMissing(prepared, options, model, signal) {
24287
24312
  }
24288
24313
  function sameMetadata2(local, remote, model) {
24289
24314
  const mine = metadata2(local, model);
24290
- return METADATA_FIELDS.every((field10) => {
24315
+ const fields = bool(local.is_deleted) ? TOMBSTONE_FIELDS : METADATA_FIELDS;
24316
+ return fields.every((field10) => {
24291
24317
  const a = mine[field10] ?? null;
24292
24318
  const b = remote[field10] ?? null;
24293
24319
  if (field10 === "embedding_model" && a === null) return true;
@@ -7605,6 +7605,7 @@ function count(value) {
7605
7605
 
7606
7606
  // src/lib/chat/resumeStream.ts
7607
7607
  init_clientConfig();
7608
+ init_redactor();
7608
7609
 
7609
7610
  // src/lib/chat/toolLoop.ts
7610
7611
  init_clientConfig();
@@ -12076,16 +12077,34 @@ async function resumeStream(options) {
12076
12077
  onData,
12077
12078
  onThinking,
12078
12079
  onFinish,
12079
- onError
12080
+ onError,
12081
+ piiRedactor
12080
12082
  } = options;
12081
12083
  const strategy = getStrategy(handle.apiType);
12082
12084
  const accumulator = createStreamAccumulator(handle.model || void 0);
12085
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
12086
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
12083
12087
  const contentSmoother = new StreamSmoother((text16) => {
12084
- if (onData) onData(text16);
12088
+ if (contentDeAnon) contentDeAnon.push(text16);
12089
+ else if (onData) onData(text16);
12085
12090
  }, smoothing);
12086
12091
  const thinkingSmoother = new StreamSmoother((text16) => {
12087
- if (onThinking) onThinking(text16);
12092
+ if (thinkingDeAnon) thinkingDeAnon.push(text16);
12093
+ else if (onThinking) onThinking(text16);
12088
12094
  }, smoothing);
12095
+ const flushOutput = () => {
12096
+ contentSmoother.flush();
12097
+ thinkingSmoother.flush();
12098
+ contentDeAnon?.flush();
12099
+ thinkingDeAnon?.flush();
12100
+ };
12101
+ const buildFinal = () => strategy.buildFinalResponse(
12102
+ piiRedactor ? {
12103
+ ...accumulator,
12104
+ content: piiRedactor.deAnonymize(accumulator.content),
12105
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
12106
+ } : accumulator
12107
+ );
12089
12108
  const idleController = new AbortController();
12090
12109
  let watchdog;
12091
12110
  let idleFired = false;
@@ -12130,9 +12149,8 @@ async function resumeStream(options) {
12130
12149
  if (signal) signal.removeEventListener("abort", onCallerAbort);
12131
12150
  };
12132
12151
  const buildInterrupted = (message) => {
12133
- contentSmoother.flush();
12134
- thinkingSmoother.flush();
12135
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
12152
+ flushOutput();
12153
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
12136
12154
  return { data, error: message, interrupted: true };
12137
12155
  };
12138
12156
  if (combinedSignal.aborted) {
@@ -12180,18 +12198,16 @@ async function resumeStream(options) {
12180
12198
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
12181
12199
  const statusCode = parseSseStatusCode(err);
12182
12200
  if (statusCode === 410) {
12183
- contentSmoother.flush();
12184
- thinkingSmoother.flush();
12201
+ flushOutput();
12185
12202
  throw new StreamExpiredError(handle.inferenceId);
12186
12203
  }
12187
12204
  if (err.name === "AbortError" || combinedSignal.aborted) {
12188
12205
  return buildInterrupted(abortMessage());
12189
12206
  }
12190
- contentSmoother.flush();
12191
- thinkingSmoother.flush();
12207
+ flushOutput();
12192
12208
  if (onError) onError(err);
12193
12209
  return {
12194
- data: strategy.buildFinalResponse(accumulator),
12210
+ data: buildFinal(),
12195
12211
  error: err.message,
12196
12212
  interrupted: false,
12197
12213
  statusCode
@@ -12207,7 +12223,9 @@ async function resumeStream(options) {
12207
12223
  draining = true;
12208
12224
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
12209
12225
  if (playbackAborted) return buildInterrupted(abortMessage());
12210
- const response = strategy.buildFinalResponse(accumulator);
12226
+ contentDeAnon?.flush();
12227
+ thinkingDeAnon?.flush();
12228
+ const response = buildFinal();
12211
12229
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
12212
12230
  if (onFinish && deliveredOutput) onFinish(response);
12213
12231
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
@@ -13218,7 +13236,7 @@ async function createRemoteMemoryPersistence(options) {
13218
13236
  ...item,
13219
13237
  memory: {
13220
13238
  ...item.memory,
13221
- content: await decrypt(item.memory.content),
13239
+ content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
13222
13240
  ...item.memory.kind_value !== void 0 && {
13223
13241
  kind_value: await decrypt(item.memory.kind_value)
13224
13242
  }
@@ -13312,7 +13330,8 @@ async function createRemoteMemoryPersistence(options) {
13312
13330
  }
13313
13331
  if (existing && existing.version !== expectedVersion) throw conflict();
13314
13332
  const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
13315
- const content = (snapshot?.content ?? decoded?.memory.content) === memory.content ? snapshot?.encryptedContent ?? existing.memory.content : await encryptField2(memory.content);
13333
+ const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
13334
+ const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
13316
13335
  const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
13317
13336
  return {
13318
13337
  body: {
@@ -28192,6 +28211,13 @@ var METADATA_FIELDS = [
28192
28211
  "published_at",
28193
28212
  "geohash"
28194
28213
  ];
28214
+ var TOMBSTONE_FIELDS = [
28215
+ "scope",
28216
+ "is_deleted",
28217
+ "embedding_model",
28218
+ "topics_updated_at",
28219
+ "last_observed_at"
28220
+ ];
28195
28221
  function text15(value) {
28196
28222
  return typeof value === "string" && value !== "" ? value : void 0;
28197
28223
  }
@@ -28339,7 +28365,8 @@ async function embedMissing(prepared, options, model, signal) {
28339
28365
  }
28340
28366
  function sameMetadata2(local, remote, model) {
28341
28367
  const mine = metadata2(local, model);
28342
- return METADATA_FIELDS.every((field10) => {
28368
+ const fields = bool(local.is_deleted) ? TOMBSTONE_FIELDS : METADATA_FIELDS;
28369
+ return fields.every((field10) => {
28343
28370
  const a = mine[field10] ?? null;
28344
28371
  const b = remote[field10] ?? null;
28345
28372
  if (field10 === "embedding_model" && a === null) return true;
@@ -36357,6 +36384,7 @@ function useChat(options) {
36357
36384
  onToolCallArgumentsDelta,
36358
36385
  onStepFinish,
36359
36386
  resumable,
36387
+ background = false,
36360
36388
  onStreamMeta,
36361
36389
  apiType: defaultApiType = "auto",
36362
36390
  smoothing,
@@ -36365,12 +36393,12 @@ function useChat(options) {
36365
36393
  piiRedaction,
36366
36394
  onPiiRedacted
36367
36395
  } = options || {};
36396
+ const keepAlive = resumable === true && background;
36368
36397
  const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
36369
36398
  const abortControllerRef = (0, import_react4.useRef)(null);
36370
36399
  const requestIdRef = (0, import_react4.useRef)(0);
36371
- const pendingInferenceRef = (0, import_react4.useRef)(
36372
- null
36373
- );
36400
+ const detachControllerRef = (0, import_react4.useRef)(null);
36401
+ const pendingInferenceRef = (0, import_react4.useRef)(null);
36374
36402
  const cancelInference = (0, import_react4.useCallback)(
36375
36403
  (inferenceId, requestToken) => {
36376
36404
  void (async () => {
@@ -36398,7 +36426,95 @@ function useChat(options) {
36398
36426
  abortControllerRef.current = null;
36399
36427
  }
36400
36428
  }, []);
36401
- (0, import_react4.useEffect)(() => stop, [stop]);
36429
+ const keepAliveRef = (0, import_react4.useRef)(keepAlive);
36430
+ keepAliveRef.current = keepAlive;
36431
+ (0, import_react4.useEffect)(
36432
+ () => () => {
36433
+ if (keepAliveRef.current) {
36434
+ if (detachControllerRef.current) {
36435
+ detachControllerRef.current.abort();
36436
+ return;
36437
+ }
36438
+ pendingInferenceRef.current = null;
36439
+ abortControllerRef.current?.abort();
36440
+ abortControllerRef.current = null;
36441
+ return;
36442
+ }
36443
+ stop();
36444
+ },
36445
+ [stop]
36446
+ );
36447
+ const resumeStream2 = (0, import_react4.useCallback)(
36448
+ async (handle, opts) => {
36449
+ const getterValidation = validateTokenGetter(getToken);
36450
+ if (!getterValidation.valid) {
36451
+ return { data: null, error: getterValidation.message, interrupted: false };
36452
+ }
36453
+ const { headless, ...resumeOpts } = opts ?? {};
36454
+ if (!headless && abortControllerRef.current) {
36455
+ return { data: null, error: "A request is already in progress", interrupted: false };
36456
+ }
36457
+ const abortController = new AbortController();
36458
+ const requestId = headless ? null : ++requestIdRef.current;
36459
+ if (!headless) {
36460
+ abortControllerRef.current = abortController;
36461
+ pendingInferenceRef.current = {
36462
+ id: handle.inferenceId,
36463
+ round: -1,
36464
+ cancel: () => cancelInference(handle.inferenceId),
36465
+ handle
36466
+ };
36467
+ setIsLoading(true);
36468
+ }
36469
+ const ownsForeground = () => requestId !== null && requestIdRef.current === requestId;
36470
+ try {
36471
+ let token;
36472
+ try {
36473
+ token = await getToken();
36474
+ } catch (err) {
36475
+ const error = err instanceof Error ? err : new Error(String(err));
36476
+ if (!headless) onError?.(error);
36477
+ return { data: null, error: error.message, interrupted: false };
36478
+ }
36479
+ const tokenValidation = validateToken(token);
36480
+ if (!tokenValidation.valid) {
36481
+ return { data: null, error: tokenValidation.message, interrupted: false };
36482
+ }
36483
+ return await resumeStream({
36484
+ handle,
36485
+ token,
36486
+ baseUrl,
36487
+ smoothing,
36488
+ signal: abortController.signal,
36489
+ piiRedactor: typeof resolvedPiiRedaction === "object" ? resolvedPiiRedaction : void 0,
36490
+ ...headless ? {} : {
36491
+ onData: (chunk) => globalOnData?.(chunk),
36492
+ onThinking: (chunk) => globalOnThinking?.(chunk),
36493
+ onFinish,
36494
+ onError
36495
+ },
36496
+ ...resumeOpts
36497
+ });
36498
+ } finally {
36499
+ if (ownsForeground()) {
36500
+ setIsLoading(false);
36501
+ if (abortControllerRef.current === abortController) abortControllerRef.current = null;
36502
+ if (pendingInferenceRef.current?.round === -1) pendingInferenceRef.current = null;
36503
+ }
36504
+ }
36505
+ },
36506
+ [
36507
+ getToken,
36508
+ baseUrl,
36509
+ smoothing,
36510
+ cancelInference,
36511
+ resolvedPiiRedaction,
36512
+ globalOnData,
36513
+ globalOnThinking,
36514
+ onFinish,
36515
+ onError
36516
+ ]
36517
+ );
36402
36518
  const sendMessage = (0, import_react4.useCallback)(
36403
36519
  async ({
36404
36520
  messages,
@@ -36426,9 +36542,12 @@ function useChat(options) {
36426
36542
  stop();
36427
36543
  const abortController = new AbortController();
36428
36544
  abortControllerRef.current = abortController;
36545
+ const detachController = keepAlive ? new AbortController() : null;
36546
+ detachControllerRef.current = detachController;
36429
36547
  const requestId = ++requestIdRef.current;
36430
36548
  setIsLoading(true);
36431
- let succeeded = false;
36549
+ let keepServerStream = false;
36550
+ const deferredErrors = [];
36432
36551
  try {
36433
36552
  const tokenGetterValidation = validateTokenGetter(getToken);
36434
36553
  if (!tokenGetterValidation.valid) {
@@ -36492,6 +36611,7 @@ function useChat(options) {
36492
36611
  apiType: requestApiType ?? defaultApiType,
36493
36612
  endpointOverride,
36494
36613
  resumable,
36614
+ detachSignal: detachController?.signal,
36495
36615
  onStreamMeta: resumable || onStreamMeta ? (meta) => {
36496
36616
  if (requestId !== requestIdRef.current || abortController.signal.aborted) {
36497
36617
  if (resumable) cancelInference(meta.inferenceId, token);
@@ -36500,12 +36620,19 @@ function useChat(options) {
36500
36620
  const previous = pendingInferenceRef.current;
36501
36621
  if (previous && previous.round === meta.round && previous.id !== meta.inferenceId)
36502
36622
  previous.cancel();
36623
+ const handle2 = {
36624
+ inferenceId: meta.inferenceId,
36625
+ apiType: resolveApiType(requestApiType ?? defaultApiType, model),
36626
+ model,
36627
+ conversationId
36628
+ };
36503
36629
  pendingInferenceRef.current = {
36504
36630
  id: meta.inferenceId,
36505
36631
  round: meta.round,
36506
- cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0
36632
+ cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0,
36633
+ handle: handle2
36507
36634
  };
36508
- onStreamMeta?.(meta);
36635
+ onStreamMeta?.({ ...handle2, round: meta.round });
36509
36636
  } : void 0,
36510
36637
  temperature,
36511
36638
  maxOutputTokens,
@@ -36527,17 +36654,44 @@ function useChat(options) {
36527
36654
  if (globalOnThinking) globalOnThinking(chunk);
36528
36655
  },
36529
36656
  onFinish,
36530
- onError,
36657
+ onError: keepAlive ? (error) => deferredErrors.push(error) : onError,
36531
36658
  onToolCall,
36532
36659
  onServerToolCall,
36533
36660
  onToolCallArgumentsDelta,
36534
- onStepFinish,
36661
+ onStepFinish: keepAlive || onStepFinish ? (event) => {
36662
+ const pending2 = pendingInferenceRef.current;
36663
+ if (keepAlive && pending2 && requestIdRef.current === requestId) {
36664
+ pendingInferenceRef.current = { ...pending2, settled: true };
36665
+ }
36666
+ onStepFinish?.(event);
36667
+ } : void 0,
36535
36668
  preProcessors,
36536
36669
  hooks,
36537
36670
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
36538
36671
  onPiiRedacted
36539
36672
  });
36540
- succeeded = result.error === null;
36673
+ const pending = pendingInferenceRef.current;
36674
+ const handle = pending && !pending.settled ? pending.handle : null;
36675
+ const detach = result.error !== null && keepAlive && handle && result.error !== "Request aborted";
36676
+ if (!detach) deferredErrors.forEach((error) => onError?.(error));
36677
+ if (result.error === null) {
36678
+ keepServerStream = true;
36679
+ return result;
36680
+ }
36681
+ if (detach) {
36682
+ keepServerStream = true;
36683
+ return {
36684
+ data: result.data,
36685
+ error: "Request detached",
36686
+ detached: true,
36687
+ resume: handle,
36688
+ toolsChecksum: result.toolsChecksum
36689
+ };
36690
+ }
36691
+ if ("detached" in result && result.detached) {
36692
+ keepServerStream = true;
36693
+ return { ...result, resume: null };
36694
+ }
36541
36695
  return result;
36542
36696
  } catch (err) {
36543
36697
  return createErrorResult(
@@ -36548,7 +36702,8 @@ function useChat(options) {
36548
36702
  if (requestIdRef.current === requestId) {
36549
36703
  setIsLoading(false);
36550
36704
  abortControllerRef.current = null;
36551
- if (!succeeded) pendingInferenceRef.current?.cancel();
36705
+ if (detachControllerRef.current === detachController) detachControllerRef.current = null;
36706
+ if (!keepServerStream) pendingInferenceRef.current?.cancel();
36552
36707
  pendingInferenceRef.current = null;
36553
36708
  }
36554
36709
  }
@@ -36567,6 +36722,7 @@ function useChat(options) {
36567
36722
  onToolCallArgumentsDelta,
36568
36723
  onStepFinish,
36569
36724
  resumable,
36725
+ keepAlive,
36570
36726
  onStreamMeta,
36571
36727
  defaultApiType,
36572
36728
  smoothing,
@@ -36579,7 +36735,8 @@ function useChat(options) {
36579
36735
  return {
36580
36736
  isLoading,
36581
36737
  sendMessage,
36582
- stop
36738
+ stop,
36739
+ resumeStream: resumeStream2
36583
36740
  };
36584
36741
  }
36585
36742
 
@@ -2890,6 +2890,8 @@ interface ResumeStreamOptions {
2890
2890
  onData?: (chunk: string) => void;
2891
2891
  /** Thinking/reasoning deltas as they replay. */
2892
2892
  onThinking?: (chunk: string) => void;
2893
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2894
+ piiRedactor?: PiiRedactor;
2893
2895
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2894
2896
  onFinish?: (response: ApiResponse) => void;
2895
2897
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -14633,6 +14635,10 @@ type SendMessageResult = {
14633
14635
  error: string;
14634
14636
  /** Checksum of tools used to generate this response */
14635
14637
  toolsChecksum?: string;
14638
+ /** True when a background stream was left running; `error` is "Request detached". */
14639
+ detached?: true;
14640
+ /** Pass to `resumeStream`; null when no inference id was issued before the detach. */
14641
+ resume?: StreamResumeHandle | null;
14636
14642
  };
14637
14643
  /**
14638
14644
  * @inline
@@ -14640,11 +14646,13 @@ type SendMessageResult = {
14640
14646
  interface UseChatOptions extends BaseUseChatOptions {
14641
14647
  /** Buffer streamed rounds so a service can verify their canonical output. */
14642
14648
  resumable?: boolean;
14643
- /** Inference identifier for each HTTP round, including client-tool continuations. */
14644
- onStreamMeta?: (meta: {
14645
- inferenceId: string;
14646
- round: number;
14647
- }) => void;
14649
+ /**
14650
+ * Keep a resumable generation running server-side when the request fails or
14651
+ * the hook unmounts; only `stop()` cancels it. Replay it with `resumeStream`.
14652
+ */
14653
+ background?: boolean;
14654
+ /** Stream handle for each HTTP round, including client-tool continuations. */
14655
+ onStreamMeta?: (meta: StreamMeta) => void;
14648
14656
  /**
14649
14657
  * Which API endpoint to use. Default: "auto"
14650
14658
  * - "auto": automatically selects the best API based on model support
@@ -14653,8 +14661,19 @@ interface UseChatOptions extends BaseUseChatOptions {
14653
14661
  */
14654
14662
  apiType?: ApiType;
14655
14663
  }
14664
+ /**
14665
+ * A resumable stream handle plus the tool-loop round that issued it.
14666
+ * @inline
14667
+ */
14668
+ type StreamMeta = StreamResumeHandle & {
14669
+ round: number;
14670
+ };
14656
14671
  type UseChatResult = BaseUseChatResult & {
14657
14672
  sendMessage: (args: SendMessageArgs) => Promise<SendMessageResult>;
14673
+ /** Replay a resumable stream from the portal buffer, from its first frame. */
14674
+ resumeStream: (handle: StreamResumeHandle, opts?: Pick<ResumeStreamOptions, "idleTimeoutMs" | "smoothing"> & {
14675
+ headless?: boolean;
14676
+ }) => Promise<ResumeStreamResult>;
14658
14677
  };
14659
14678
  /**
14660
14679
  * A React hook for managing chat completions with authentication.
@@ -2890,6 +2890,8 @@ interface ResumeStreamOptions {
2890
2890
  onData?: (chunk: string) => void;
2891
2891
  /** Thinking/reasoning deltas as they replay. */
2892
2892
  onThinking?: (chunk: string) => void;
2893
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2894
+ piiRedactor?: PiiRedactor;
2893
2895
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2894
2896
  onFinish?: (response: ApiResponse) => void;
2895
2897
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -14633,6 +14635,10 @@ type SendMessageResult = {
14633
14635
  error: string;
14634
14636
  /** Checksum of tools used to generate this response */
14635
14637
  toolsChecksum?: string;
14638
+ /** True when a background stream was left running; `error` is "Request detached". */
14639
+ detached?: true;
14640
+ /** Pass to `resumeStream`; null when no inference id was issued before the detach. */
14641
+ resume?: StreamResumeHandle | null;
14636
14642
  };
14637
14643
  /**
14638
14644
  * @inline
@@ -14640,11 +14646,13 @@ type SendMessageResult = {
14640
14646
  interface UseChatOptions extends BaseUseChatOptions {
14641
14647
  /** Buffer streamed rounds so a service can verify their canonical output. */
14642
14648
  resumable?: boolean;
14643
- /** Inference identifier for each HTTP round, including client-tool continuations. */
14644
- onStreamMeta?: (meta: {
14645
- inferenceId: string;
14646
- round: number;
14647
- }) => void;
14649
+ /**
14650
+ * Keep a resumable generation running server-side when the request fails or
14651
+ * the hook unmounts; only `stop()` cancels it. Replay it with `resumeStream`.
14652
+ */
14653
+ background?: boolean;
14654
+ /** Stream handle for each HTTP round, including client-tool continuations. */
14655
+ onStreamMeta?: (meta: StreamMeta) => void;
14648
14656
  /**
14649
14657
  * Which API endpoint to use. Default: "auto"
14650
14658
  * - "auto": automatically selects the best API based on model support
@@ -14653,8 +14661,19 @@ interface UseChatOptions extends BaseUseChatOptions {
14653
14661
  */
14654
14662
  apiType?: ApiType;
14655
14663
  }
14664
+ /**
14665
+ * A resumable stream handle plus the tool-loop round that issued it.
14666
+ * @inline
14667
+ */
14668
+ type StreamMeta = StreamResumeHandle & {
14669
+ round: number;
14670
+ };
14656
14671
  type UseChatResult = BaseUseChatResult & {
14657
14672
  sendMessage: (args: SendMessageArgs) => Promise<SendMessageResult>;
14673
+ /** Replay a resumable stream from the portal buffer, from its first frame. */
14674
+ resumeStream: (handle: StreamResumeHandle, opts?: Pick<ResumeStreamOptions, "idleTimeoutMs" | "smoothing"> & {
14675
+ headless?: boolean;
14676
+ }) => Promise<ResumeStreamResult>;
14658
14677
  };
14659
14678
  /**
14660
14679
  * A React hook for managing chat completions with authentication.
@@ -10208,16 +10208,34 @@ async function resumeStream(options) {
10208
10208
  onData,
10209
10209
  onThinking,
10210
10210
  onFinish,
10211
- onError
10211
+ onError,
10212
+ piiRedactor
10212
10213
  } = options;
10213
10214
  const strategy = getStrategy(handle.apiType);
10214
10215
  const accumulator = createStreamAccumulator(handle.model || void 0);
10216
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
10217
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
10215
10218
  const contentSmoother = new StreamSmoother((text16) => {
10216
- if (onData) onData(text16);
10219
+ if (contentDeAnon) contentDeAnon.push(text16);
10220
+ else if (onData) onData(text16);
10217
10221
  }, smoothing);
10218
10222
  const thinkingSmoother = new StreamSmoother((text16) => {
10219
- if (onThinking) onThinking(text16);
10223
+ if (thinkingDeAnon) thinkingDeAnon.push(text16);
10224
+ else if (onThinking) onThinking(text16);
10220
10225
  }, smoothing);
10226
+ const flushOutput = () => {
10227
+ contentSmoother.flush();
10228
+ thinkingSmoother.flush();
10229
+ contentDeAnon?.flush();
10230
+ thinkingDeAnon?.flush();
10231
+ };
10232
+ const buildFinal = () => strategy.buildFinalResponse(
10233
+ piiRedactor ? {
10234
+ ...accumulator,
10235
+ content: piiRedactor.deAnonymize(accumulator.content),
10236
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
10237
+ } : accumulator
10238
+ );
10221
10239
  const idleController = new AbortController();
10222
10240
  let watchdog;
10223
10241
  let idleFired = false;
@@ -10262,9 +10280,8 @@ async function resumeStream(options) {
10262
10280
  if (signal) signal.removeEventListener("abort", onCallerAbort);
10263
10281
  };
10264
10282
  const buildInterrupted = (message) => {
10265
- contentSmoother.flush();
10266
- thinkingSmoother.flush();
10267
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
10283
+ flushOutput();
10284
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
10268
10285
  return { data, error: message, interrupted: true };
10269
10286
  };
10270
10287
  if (combinedSignal.aborted) {
@@ -10312,18 +10329,16 @@ async function resumeStream(options) {
10312
10329
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
10313
10330
  const statusCode = parseSseStatusCode(err);
10314
10331
  if (statusCode === 410) {
10315
- contentSmoother.flush();
10316
- thinkingSmoother.flush();
10332
+ flushOutput();
10317
10333
  throw new StreamExpiredError(handle.inferenceId);
10318
10334
  }
10319
10335
  if (err.name === "AbortError" || combinedSignal.aborted) {
10320
10336
  return buildInterrupted(abortMessage());
10321
10337
  }
10322
- contentSmoother.flush();
10323
- thinkingSmoother.flush();
10338
+ flushOutput();
10324
10339
  if (onError) onError(err);
10325
10340
  return {
10326
- data: strategy.buildFinalResponse(accumulator),
10341
+ data: buildFinal(),
10327
10342
  error: err.message,
10328
10343
  interrupted: false,
10329
10344
  statusCode
@@ -10339,7 +10354,9 @@ async function resumeStream(options) {
10339
10354
  draining = true;
10340
10355
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
10341
10356
  if (playbackAborted) return buildInterrupted(abortMessage());
10342
- const response = strategy.buildFinalResponse(accumulator);
10357
+ contentDeAnon?.flush();
10358
+ thinkingDeAnon?.flush();
10359
+ const response = buildFinal();
10343
10360
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
10344
10361
  if (onFinish && deliveredOutput) onFinish(response);
10345
10362
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
@@ -11345,7 +11362,7 @@ async function createRemoteMemoryPersistence(options) {
11345
11362
  ...item,
11346
11363
  memory: {
11347
11364
  ...item.memory,
11348
- content: await decrypt(item.memory.content),
11365
+ content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
11349
11366
  ...item.memory.kind_value !== void 0 && {
11350
11367
  kind_value: await decrypt(item.memory.kind_value)
11351
11368
  }
@@ -11439,7 +11456,8 @@ async function createRemoteMemoryPersistence(options) {
11439
11456
  }
11440
11457
  if (existing && existing.version !== expectedVersion) throw conflict();
11441
11458
  const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
11442
- const content = (snapshot?.content ?? decoded?.memory.content) === memory.content ? snapshot?.encryptedContent ?? existing.memory.content : await encryptField2(memory.content);
11459
+ const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
11460
+ const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
11443
11461
  const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
11444
11462
  return {
11445
11463
  body: {
@@ -26266,6 +26284,13 @@ var METADATA_FIELDS = [
26266
26284
  "published_at",
26267
26285
  "geohash"
26268
26286
  ];
26287
+ var TOMBSTONE_FIELDS = [
26288
+ "scope",
26289
+ "is_deleted",
26290
+ "embedding_model",
26291
+ "topics_updated_at",
26292
+ "last_observed_at"
26293
+ ];
26269
26294
  function text15(value) {
26270
26295
  return typeof value === "string" && value !== "" ? value : void 0;
26271
26296
  }
@@ -26413,7 +26438,8 @@ async function embedMissing(prepared, options, model, signal) {
26413
26438
  }
26414
26439
  function sameMetadata2(local, remote, model) {
26415
26440
  const mine = metadata2(local, model);
26416
- return METADATA_FIELDS.every((field10) => {
26441
+ const fields = bool(local.is_deleted) ? TOMBSTONE_FIELDS : METADATA_FIELDS;
26442
+ return fields.every((field10) => {
26417
26443
  const a = mine[field10] ?? null;
26418
26444
  const b = remote[field10] ?? null;
26419
26445
  if (field10 === "embedding_model" && a === null) return true;
@@ -34429,6 +34455,7 @@ function useChat(options) {
34429
34455
  onToolCallArgumentsDelta,
34430
34456
  onStepFinish,
34431
34457
  resumable,
34458
+ background = false,
34432
34459
  onStreamMeta,
34433
34460
  apiType: defaultApiType = "auto",
34434
34461
  smoothing,
@@ -34437,12 +34464,12 @@ function useChat(options) {
34437
34464
  piiRedaction,
34438
34465
  onPiiRedacted
34439
34466
  } = options || {};
34467
+ const keepAlive = resumable === true && background;
34440
34468
  const [isLoading, setIsLoading] = useState4(false);
34441
34469
  const abortControllerRef = useRef3(null);
34442
34470
  const requestIdRef = useRef3(0);
34443
- const pendingInferenceRef = useRef3(
34444
- null
34445
- );
34471
+ const detachControllerRef = useRef3(null);
34472
+ const pendingInferenceRef = useRef3(null);
34446
34473
  const cancelInference = useCallback3(
34447
34474
  (inferenceId, requestToken) => {
34448
34475
  void (async () => {
@@ -34470,7 +34497,95 @@ function useChat(options) {
34470
34497
  abortControllerRef.current = null;
34471
34498
  }
34472
34499
  }, []);
34473
- useEffect3(() => stop, [stop]);
34500
+ const keepAliveRef = useRef3(keepAlive);
34501
+ keepAliveRef.current = keepAlive;
34502
+ useEffect3(
34503
+ () => () => {
34504
+ if (keepAliveRef.current) {
34505
+ if (detachControllerRef.current) {
34506
+ detachControllerRef.current.abort();
34507
+ return;
34508
+ }
34509
+ pendingInferenceRef.current = null;
34510
+ abortControllerRef.current?.abort();
34511
+ abortControllerRef.current = null;
34512
+ return;
34513
+ }
34514
+ stop();
34515
+ },
34516
+ [stop]
34517
+ );
34518
+ const resumeStream2 = useCallback3(
34519
+ async (handle, opts) => {
34520
+ const getterValidation = validateTokenGetter(getToken);
34521
+ if (!getterValidation.valid) {
34522
+ return { data: null, error: getterValidation.message, interrupted: false };
34523
+ }
34524
+ const { headless, ...resumeOpts } = opts ?? {};
34525
+ if (!headless && abortControllerRef.current) {
34526
+ return { data: null, error: "A request is already in progress", interrupted: false };
34527
+ }
34528
+ const abortController = new AbortController();
34529
+ const requestId = headless ? null : ++requestIdRef.current;
34530
+ if (!headless) {
34531
+ abortControllerRef.current = abortController;
34532
+ pendingInferenceRef.current = {
34533
+ id: handle.inferenceId,
34534
+ round: -1,
34535
+ cancel: () => cancelInference(handle.inferenceId),
34536
+ handle
34537
+ };
34538
+ setIsLoading(true);
34539
+ }
34540
+ const ownsForeground = () => requestId !== null && requestIdRef.current === requestId;
34541
+ try {
34542
+ let token;
34543
+ try {
34544
+ token = await getToken();
34545
+ } catch (err) {
34546
+ const error = err instanceof Error ? err : new Error(String(err));
34547
+ if (!headless) onError?.(error);
34548
+ return { data: null, error: error.message, interrupted: false };
34549
+ }
34550
+ const tokenValidation = validateToken(token);
34551
+ if (!tokenValidation.valid) {
34552
+ return { data: null, error: tokenValidation.message, interrupted: false };
34553
+ }
34554
+ return await resumeStream({
34555
+ handle,
34556
+ token,
34557
+ baseUrl,
34558
+ smoothing,
34559
+ signal: abortController.signal,
34560
+ piiRedactor: typeof resolvedPiiRedaction === "object" ? resolvedPiiRedaction : void 0,
34561
+ ...headless ? {} : {
34562
+ onData: (chunk) => globalOnData?.(chunk),
34563
+ onThinking: (chunk) => globalOnThinking?.(chunk),
34564
+ onFinish,
34565
+ onError
34566
+ },
34567
+ ...resumeOpts
34568
+ });
34569
+ } finally {
34570
+ if (ownsForeground()) {
34571
+ setIsLoading(false);
34572
+ if (abortControllerRef.current === abortController) abortControllerRef.current = null;
34573
+ if (pendingInferenceRef.current?.round === -1) pendingInferenceRef.current = null;
34574
+ }
34575
+ }
34576
+ },
34577
+ [
34578
+ getToken,
34579
+ baseUrl,
34580
+ smoothing,
34581
+ cancelInference,
34582
+ resolvedPiiRedaction,
34583
+ globalOnData,
34584
+ globalOnThinking,
34585
+ onFinish,
34586
+ onError
34587
+ ]
34588
+ );
34474
34589
  const sendMessage = useCallback3(
34475
34590
  async ({
34476
34591
  messages,
@@ -34498,9 +34613,12 @@ function useChat(options) {
34498
34613
  stop();
34499
34614
  const abortController = new AbortController();
34500
34615
  abortControllerRef.current = abortController;
34616
+ const detachController = keepAlive ? new AbortController() : null;
34617
+ detachControllerRef.current = detachController;
34501
34618
  const requestId = ++requestIdRef.current;
34502
34619
  setIsLoading(true);
34503
- let succeeded = false;
34620
+ let keepServerStream = false;
34621
+ const deferredErrors = [];
34504
34622
  try {
34505
34623
  const tokenGetterValidation = validateTokenGetter(getToken);
34506
34624
  if (!tokenGetterValidation.valid) {
@@ -34564,6 +34682,7 @@ function useChat(options) {
34564
34682
  apiType: requestApiType ?? defaultApiType,
34565
34683
  endpointOverride,
34566
34684
  resumable,
34685
+ detachSignal: detachController?.signal,
34567
34686
  onStreamMeta: resumable || onStreamMeta ? (meta) => {
34568
34687
  if (requestId !== requestIdRef.current || abortController.signal.aborted) {
34569
34688
  if (resumable) cancelInference(meta.inferenceId, token);
@@ -34572,12 +34691,19 @@ function useChat(options) {
34572
34691
  const previous = pendingInferenceRef.current;
34573
34692
  if (previous && previous.round === meta.round && previous.id !== meta.inferenceId)
34574
34693
  previous.cancel();
34694
+ const handle2 = {
34695
+ inferenceId: meta.inferenceId,
34696
+ apiType: resolveApiType(requestApiType ?? defaultApiType, model),
34697
+ model,
34698
+ conversationId
34699
+ };
34575
34700
  pendingInferenceRef.current = {
34576
34701
  id: meta.inferenceId,
34577
34702
  round: meta.round,
34578
- cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0
34703
+ cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0,
34704
+ handle: handle2
34579
34705
  };
34580
- onStreamMeta?.(meta);
34706
+ onStreamMeta?.({ ...handle2, round: meta.round });
34581
34707
  } : void 0,
34582
34708
  temperature,
34583
34709
  maxOutputTokens,
@@ -34599,17 +34725,44 @@ function useChat(options) {
34599
34725
  if (globalOnThinking) globalOnThinking(chunk);
34600
34726
  },
34601
34727
  onFinish,
34602
- onError,
34728
+ onError: keepAlive ? (error) => deferredErrors.push(error) : onError,
34603
34729
  onToolCall,
34604
34730
  onServerToolCall,
34605
34731
  onToolCallArgumentsDelta,
34606
- onStepFinish,
34732
+ onStepFinish: keepAlive || onStepFinish ? (event) => {
34733
+ const pending2 = pendingInferenceRef.current;
34734
+ if (keepAlive && pending2 && requestIdRef.current === requestId) {
34735
+ pendingInferenceRef.current = { ...pending2, settled: true };
34736
+ }
34737
+ onStepFinish?.(event);
34738
+ } : void 0,
34607
34739
  preProcessors,
34608
34740
  hooks,
34609
34741
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
34610
34742
  onPiiRedacted
34611
34743
  });
34612
- succeeded = result.error === null;
34744
+ const pending = pendingInferenceRef.current;
34745
+ const handle = pending && !pending.settled ? pending.handle : null;
34746
+ const detach = result.error !== null && keepAlive && handle && result.error !== "Request aborted";
34747
+ if (!detach) deferredErrors.forEach((error) => onError?.(error));
34748
+ if (result.error === null) {
34749
+ keepServerStream = true;
34750
+ return result;
34751
+ }
34752
+ if (detach) {
34753
+ keepServerStream = true;
34754
+ return {
34755
+ data: result.data,
34756
+ error: "Request detached",
34757
+ detached: true,
34758
+ resume: handle,
34759
+ toolsChecksum: result.toolsChecksum
34760
+ };
34761
+ }
34762
+ if ("detached" in result && result.detached) {
34763
+ keepServerStream = true;
34764
+ return { ...result, resume: null };
34765
+ }
34613
34766
  return result;
34614
34767
  } catch (err) {
34615
34768
  return createErrorResult(
@@ -34620,7 +34773,8 @@ function useChat(options) {
34620
34773
  if (requestIdRef.current === requestId) {
34621
34774
  setIsLoading(false);
34622
34775
  abortControllerRef.current = null;
34623
- if (!succeeded) pendingInferenceRef.current?.cancel();
34776
+ if (detachControllerRef.current === detachController) detachControllerRef.current = null;
34777
+ if (!keepServerStream) pendingInferenceRef.current?.cancel();
34624
34778
  pendingInferenceRef.current = null;
34625
34779
  }
34626
34780
  }
@@ -34639,6 +34793,7 @@ function useChat(options) {
34639
34793
  onToolCallArgumentsDelta,
34640
34794
  onStepFinish,
34641
34795
  resumable,
34796
+ keepAlive,
34642
34797
  onStreamMeta,
34643
34798
  defaultApiType,
34644
34799
  smoothing,
@@ -34651,7 +34806,8 @@ function useChat(options) {
34651
34806
  return {
34652
34807
  isLoading,
34653
34808
  sendMessage,
34654
- stop
34809
+ stop,
34810
+ resumeStream: resumeStream2
34655
34811
  };
34656
34812
  }
34657
34813
 
@@ -21163,6 +21163,7 @@ function createCryptoPricePreProcessor(options = {}) {
21163
21163
 
21164
21164
  // src/lib/chat/resumeStream.ts
21165
21165
  init_clientConfig();
21166
+ init_redactor();
21166
21167
 
21167
21168
  // src/lib/chat/toolLoop.ts
21168
21169
  init_clientConfig();
@@ -25385,16 +25386,34 @@ async function resumeStream(options) {
25385
25386
  onData,
25386
25387
  onThinking,
25387
25388
  onFinish,
25388
- onError
25389
+ onError,
25390
+ piiRedactor
25389
25391
  } = options;
25390
25392
  const strategy = getStrategy(handle.apiType);
25391
25393
  const accumulator = createStreamAccumulator(handle.model || void 0);
25394
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
25395
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
25392
25396
  const contentSmoother = new StreamSmoother((text12) => {
25393
- if (onData) onData(text12);
25397
+ if (contentDeAnon) contentDeAnon.push(text12);
25398
+ else if (onData) onData(text12);
25394
25399
  }, smoothing);
25395
25400
  const thinkingSmoother = new StreamSmoother((text12) => {
25396
- if (onThinking) onThinking(text12);
25401
+ if (thinkingDeAnon) thinkingDeAnon.push(text12);
25402
+ else if (onThinking) onThinking(text12);
25397
25403
  }, smoothing);
25404
+ const flushOutput = () => {
25405
+ contentSmoother.flush();
25406
+ thinkingSmoother.flush();
25407
+ contentDeAnon?.flush();
25408
+ thinkingDeAnon?.flush();
25409
+ };
25410
+ const buildFinal = () => strategy.buildFinalResponse(
25411
+ piiRedactor ? {
25412
+ ...accumulator,
25413
+ content: piiRedactor.deAnonymize(accumulator.content),
25414
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
25415
+ } : accumulator
25416
+ );
25398
25417
  const idleController = new AbortController();
25399
25418
  let watchdog;
25400
25419
  let idleFired = false;
@@ -25439,9 +25458,8 @@ async function resumeStream(options) {
25439
25458
  if (signal) signal.removeEventListener("abort", onCallerAbort);
25440
25459
  };
25441
25460
  const buildInterrupted = (message) => {
25442
- contentSmoother.flush();
25443
- thinkingSmoother.flush();
25444
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
25461
+ flushOutput();
25462
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
25445
25463
  return { data, error: message, interrupted: true };
25446
25464
  };
25447
25465
  if (combinedSignal.aborted) {
@@ -25489,18 +25507,16 @@ async function resumeStream(options) {
25489
25507
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
25490
25508
  const statusCode = parseSseStatusCode(err);
25491
25509
  if (statusCode === 410) {
25492
- contentSmoother.flush();
25493
- thinkingSmoother.flush();
25510
+ flushOutput();
25494
25511
  throw new StreamExpiredError(handle.inferenceId);
25495
25512
  }
25496
25513
  if (err.name === "AbortError" || combinedSignal.aborted) {
25497
25514
  return buildInterrupted(abortMessage());
25498
25515
  }
25499
- contentSmoother.flush();
25500
- thinkingSmoother.flush();
25516
+ flushOutput();
25501
25517
  if (onError) onError(err);
25502
25518
  return {
25503
- data: strategy.buildFinalResponse(accumulator),
25519
+ data: buildFinal(),
25504
25520
  error: err.message,
25505
25521
  interrupted: false,
25506
25522
  statusCode
@@ -25516,7 +25532,9 @@ async function resumeStream(options) {
25516
25532
  draining = true;
25517
25533
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
25518
25534
  if (playbackAborted) return buildInterrupted(abortMessage());
25519
- const response = strategy.buildFinalResponse(accumulator);
25535
+ contentDeAnon?.flush();
25536
+ thinkingDeAnon?.flush();
25537
+ const response = buildFinal();
25520
25538
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
25521
25539
  if (onFinish && deliveredOutput) onFinish(response);
25522
25540
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
@@ -2470,6 +2470,8 @@ interface ResumeStreamOptions {
2470
2470
  onData?: (chunk: string) => void;
2471
2471
  /** Thinking/reasoning deltas as they replay. */
2472
2472
  onThinking?: (chunk: string) => void;
2473
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2474
+ piiRedactor?: PiiRedactor;
2473
2475
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2474
2476
  onFinish?: (response: ApiResponse) => void;
2475
2477
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -2470,6 +2470,8 @@ interface ResumeStreamOptions {
2470
2470
  onData?: (chunk: string) => void;
2471
2471
  /** Thinking/reasoning deltas as they replay. */
2472
2472
  onThinking?: (chunk: string) => void;
2473
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
2474
+ piiRedactor?: PiiRedactor;
2473
2475
  /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2474
2476
  onFinish?: (response: ApiResponse) => void;
2475
2477
  /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
@@ -23655,16 +23655,34 @@ async function resumeStream(options) {
23655
23655
  onData,
23656
23656
  onThinking,
23657
23657
  onFinish,
23658
- onError
23658
+ onError,
23659
+ piiRedactor
23659
23660
  } = options;
23660
23661
  const strategy = getStrategy(handle.apiType);
23661
23662
  const accumulator = createStreamAccumulator(handle.model || void 0);
23663
+ const contentDeAnon = piiRedactor && onData ? createStreamingDeAnonymizer(piiRedactor, onData) : null;
23664
+ const thinkingDeAnon = piiRedactor && onThinking ? createStreamingDeAnonymizer(piiRedactor, onThinking) : null;
23662
23665
  const contentSmoother = new StreamSmoother((text12) => {
23663
- if (onData) onData(text12);
23666
+ if (contentDeAnon) contentDeAnon.push(text12);
23667
+ else if (onData) onData(text12);
23664
23668
  }, smoothing);
23665
23669
  const thinkingSmoother = new StreamSmoother((text12) => {
23666
- if (onThinking) onThinking(text12);
23670
+ if (thinkingDeAnon) thinkingDeAnon.push(text12);
23671
+ else if (onThinking) onThinking(text12);
23667
23672
  }, smoothing);
23673
+ const flushOutput = () => {
23674
+ contentSmoother.flush();
23675
+ thinkingSmoother.flush();
23676
+ contentDeAnon?.flush();
23677
+ thinkingDeAnon?.flush();
23678
+ };
23679
+ const buildFinal = () => strategy.buildFinalResponse(
23680
+ piiRedactor ? {
23681
+ ...accumulator,
23682
+ content: piiRedactor.deAnonymize(accumulator.content),
23683
+ thinking: piiRedactor.deAnonymize(accumulator.thinking)
23684
+ } : accumulator
23685
+ );
23668
23686
  const idleController = new AbortController();
23669
23687
  let watchdog;
23670
23688
  let idleFired = false;
@@ -23709,9 +23727,8 @@ async function resumeStream(options) {
23709
23727
  if (signal) signal.removeEventListener("abort", onCallerAbort);
23710
23728
  };
23711
23729
  const buildInterrupted = (message) => {
23712
- contentSmoother.flush();
23713
- thinkingSmoother.flush();
23714
- const data = accumulator.toolCalls.size > 0 ? stripToolCalls(strategy.buildFinalResponse(accumulator)) : strategy.buildFinalResponse(accumulator);
23730
+ flushOutput();
23731
+ const data = accumulator.toolCalls.size > 0 ? stripToolCalls(buildFinal()) : buildFinal();
23715
23732
  return { data, error: message, interrupted: true };
23716
23733
  };
23717
23734
  if (combinedSignal.aborted) {
@@ -23759,18 +23776,16 @@ async function resumeStream(options) {
23759
23776
  const err = replayErr instanceof Error ? replayErr : new Error(String(replayErr));
23760
23777
  const statusCode = parseSseStatusCode(err);
23761
23778
  if (statusCode === 410) {
23762
- contentSmoother.flush();
23763
- thinkingSmoother.flush();
23779
+ flushOutput();
23764
23780
  throw new StreamExpiredError(handle.inferenceId);
23765
23781
  }
23766
23782
  if (err.name === "AbortError" || combinedSignal.aborted) {
23767
23783
  return buildInterrupted(abortMessage());
23768
23784
  }
23769
- contentSmoother.flush();
23770
- thinkingSmoother.flush();
23785
+ flushOutput();
23771
23786
  if (onError) onError(err);
23772
23787
  return {
23773
- data: strategy.buildFinalResponse(accumulator),
23788
+ data: buildFinal(),
23774
23789
  error: err.message,
23775
23790
  interrupted: false,
23776
23791
  statusCode
@@ -23786,7 +23801,9 @@ async function resumeStream(options) {
23786
23801
  draining = true;
23787
23802
  await Promise.all([contentSmoother.drain(), thinkingSmoother.drain()]);
23788
23803
  if (playbackAborted) return buildInterrupted(abortMessage());
23789
- const response = strategy.buildFinalResponse(accumulator);
23804
+ contentDeAnon?.flush();
23805
+ thinkingDeAnon?.flush();
23806
+ const response = buildFinal();
23790
23807
  const deliveredOutput = emittedOutput || (accumulator.toolCallEvents?.length ?? 0) > 0;
23791
23808
  if (onFinish && deliveredOutput) onFinish(response);
23792
23809
  return { data: response, error: null, interrupted: false, empty: !deliveredOutput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "10.5.3",
3
+ "version": "10.6.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",