@anuma/sdk 10.5.4 → 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 };
@@ -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 };
@@ -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 };
@@ -36366,6 +36384,7 @@ function useChat(options) {
36366
36384
  onToolCallArgumentsDelta,
36367
36385
  onStepFinish,
36368
36386
  resumable,
36387
+ background = false,
36369
36388
  onStreamMeta,
36370
36389
  apiType: defaultApiType = "auto",
36371
36390
  smoothing,
@@ -36374,12 +36393,12 @@ function useChat(options) {
36374
36393
  piiRedaction,
36375
36394
  onPiiRedacted
36376
36395
  } = options || {};
36396
+ const keepAlive = resumable === true && background;
36377
36397
  const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
36378
36398
  const abortControllerRef = (0, import_react4.useRef)(null);
36379
36399
  const requestIdRef = (0, import_react4.useRef)(0);
36380
- const pendingInferenceRef = (0, import_react4.useRef)(
36381
- null
36382
- );
36400
+ const detachControllerRef = (0, import_react4.useRef)(null);
36401
+ const pendingInferenceRef = (0, import_react4.useRef)(null);
36383
36402
  const cancelInference = (0, import_react4.useCallback)(
36384
36403
  (inferenceId, requestToken) => {
36385
36404
  void (async () => {
@@ -36407,7 +36426,95 @@ function useChat(options) {
36407
36426
  abortControllerRef.current = null;
36408
36427
  }
36409
36428
  }, []);
36410
- (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
+ );
36411
36518
  const sendMessage = (0, import_react4.useCallback)(
36412
36519
  async ({
36413
36520
  messages,
@@ -36435,9 +36542,12 @@ function useChat(options) {
36435
36542
  stop();
36436
36543
  const abortController = new AbortController();
36437
36544
  abortControllerRef.current = abortController;
36545
+ const detachController = keepAlive ? new AbortController() : null;
36546
+ detachControllerRef.current = detachController;
36438
36547
  const requestId = ++requestIdRef.current;
36439
36548
  setIsLoading(true);
36440
- let succeeded = false;
36549
+ let keepServerStream = false;
36550
+ const deferredErrors = [];
36441
36551
  try {
36442
36552
  const tokenGetterValidation = validateTokenGetter(getToken);
36443
36553
  if (!tokenGetterValidation.valid) {
@@ -36501,6 +36611,7 @@ function useChat(options) {
36501
36611
  apiType: requestApiType ?? defaultApiType,
36502
36612
  endpointOverride,
36503
36613
  resumable,
36614
+ detachSignal: detachController?.signal,
36504
36615
  onStreamMeta: resumable || onStreamMeta ? (meta) => {
36505
36616
  if (requestId !== requestIdRef.current || abortController.signal.aborted) {
36506
36617
  if (resumable) cancelInference(meta.inferenceId, token);
@@ -36509,12 +36620,19 @@ function useChat(options) {
36509
36620
  const previous = pendingInferenceRef.current;
36510
36621
  if (previous && previous.round === meta.round && previous.id !== meta.inferenceId)
36511
36622
  previous.cancel();
36623
+ const handle2 = {
36624
+ inferenceId: meta.inferenceId,
36625
+ apiType: resolveApiType(requestApiType ?? defaultApiType, model),
36626
+ model,
36627
+ conversationId
36628
+ };
36512
36629
  pendingInferenceRef.current = {
36513
36630
  id: meta.inferenceId,
36514
36631
  round: meta.round,
36515
- cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0
36632
+ cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0,
36633
+ handle: handle2
36516
36634
  };
36517
- onStreamMeta?.(meta);
36635
+ onStreamMeta?.({ ...handle2, round: meta.round });
36518
36636
  } : void 0,
36519
36637
  temperature,
36520
36638
  maxOutputTokens,
@@ -36536,17 +36654,44 @@ function useChat(options) {
36536
36654
  if (globalOnThinking) globalOnThinking(chunk);
36537
36655
  },
36538
36656
  onFinish,
36539
- onError,
36657
+ onError: keepAlive ? (error) => deferredErrors.push(error) : onError,
36540
36658
  onToolCall,
36541
36659
  onServerToolCall,
36542
36660
  onToolCallArgumentsDelta,
36543
- 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,
36544
36668
  preProcessors,
36545
36669
  hooks,
36546
36670
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
36547
36671
  onPiiRedacted
36548
36672
  });
36549
- 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
+ }
36550
36695
  return result;
36551
36696
  } catch (err) {
36552
36697
  return createErrorResult(
@@ -36557,7 +36702,8 @@ function useChat(options) {
36557
36702
  if (requestIdRef.current === requestId) {
36558
36703
  setIsLoading(false);
36559
36704
  abortControllerRef.current = null;
36560
- if (!succeeded) pendingInferenceRef.current?.cancel();
36705
+ if (detachControllerRef.current === detachController) detachControllerRef.current = null;
36706
+ if (!keepServerStream) pendingInferenceRef.current?.cancel();
36561
36707
  pendingInferenceRef.current = null;
36562
36708
  }
36563
36709
  }
@@ -36576,6 +36722,7 @@ function useChat(options) {
36576
36722
  onToolCallArgumentsDelta,
36577
36723
  onStepFinish,
36578
36724
  resumable,
36725
+ keepAlive,
36579
36726
  onStreamMeta,
36580
36727
  defaultApiType,
36581
36728
  smoothing,
@@ -36588,7 +36735,8 @@ function useChat(options) {
36588
36735
  return {
36589
36736
  isLoading,
36590
36737
  sendMessage,
36591
- stop
36738
+ stop,
36739
+ resumeStream: resumeStream2
36592
36740
  };
36593
36741
  }
36594
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 };
@@ -34438,6 +34455,7 @@ function useChat(options) {
34438
34455
  onToolCallArgumentsDelta,
34439
34456
  onStepFinish,
34440
34457
  resumable,
34458
+ background = false,
34441
34459
  onStreamMeta,
34442
34460
  apiType: defaultApiType = "auto",
34443
34461
  smoothing,
@@ -34446,12 +34464,12 @@ function useChat(options) {
34446
34464
  piiRedaction,
34447
34465
  onPiiRedacted
34448
34466
  } = options || {};
34467
+ const keepAlive = resumable === true && background;
34449
34468
  const [isLoading, setIsLoading] = useState4(false);
34450
34469
  const abortControllerRef = useRef3(null);
34451
34470
  const requestIdRef = useRef3(0);
34452
- const pendingInferenceRef = useRef3(
34453
- null
34454
- );
34471
+ const detachControllerRef = useRef3(null);
34472
+ const pendingInferenceRef = useRef3(null);
34455
34473
  const cancelInference = useCallback3(
34456
34474
  (inferenceId, requestToken) => {
34457
34475
  void (async () => {
@@ -34479,7 +34497,95 @@ function useChat(options) {
34479
34497
  abortControllerRef.current = null;
34480
34498
  }
34481
34499
  }, []);
34482
- 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
+ );
34483
34589
  const sendMessage = useCallback3(
34484
34590
  async ({
34485
34591
  messages,
@@ -34507,9 +34613,12 @@ function useChat(options) {
34507
34613
  stop();
34508
34614
  const abortController = new AbortController();
34509
34615
  abortControllerRef.current = abortController;
34616
+ const detachController = keepAlive ? new AbortController() : null;
34617
+ detachControllerRef.current = detachController;
34510
34618
  const requestId = ++requestIdRef.current;
34511
34619
  setIsLoading(true);
34512
- let succeeded = false;
34620
+ let keepServerStream = false;
34621
+ const deferredErrors = [];
34513
34622
  try {
34514
34623
  const tokenGetterValidation = validateTokenGetter(getToken);
34515
34624
  if (!tokenGetterValidation.valid) {
@@ -34573,6 +34682,7 @@ function useChat(options) {
34573
34682
  apiType: requestApiType ?? defaultApiType,
34574
34683
  endpointOverride,
34575
34684
  resumable,
34685
+ detachSignal: detachController?.signal,
34576
34686
  onStreamMeta: resumable || onStreamMeta ? (meta) => {
34577
34687
  if (requestId !== requestIdRef.current || abortController.signal.aborted) {
34578
34688
  if (resumable) cancelInference(meta.inferenceId, token);
@@ -34581,12 +34691,19 @@ function useChat(options) {
34581
34691
  const previous = pendingInferenceRef.current;
34582
34692
  if (previous && previous.round === meta.round && previous.id !== meta.inferenceId)
34583
34693
  previous.cancel();
34694
+ const handle2 = {
34695
+ inferenceId: meta.inferenceId,
34696
+ apiType: resolveApiType(requestApiType ?? defaultApiType, model),
34697
+ model,
34698
+ conversationId
34699
+ };
34584
34700
  pendingInferenceRef.current = {
34585
34701
  id: meta.inferenceId,
34586
34702
  round: meta.round,
34587
- cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0
34703
+ cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0,
34704
+ handle: handle2
34588
34705
  };
34589
- onStreamMeta?.(meta);
34706
+ onStreamMeta?.({ ...handle2, round: meta.round });
34590
34707
  } : void 0,
34591
34708
  temperature,
34592
34709
  maxOutputTokens,
@@ -34608,17 +34725,44 @@ function useChat(options) {
34608
34725
  if (globalOnThinking) globalOnThinking(chunk);
34609
34726
  },
34610
34727
  onFinish,
34611
- onError,
34728
+ onError: keepAlive ? (error) => deferredErrors.push(error) : onError,
34612
34729
  onToolCall,
34613
34730
  onServerToolCall,
34614
34731
  onToolCallArgumentsDelta,
34615
- 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,
34616
34739
  preProcessors,
34617
34740
  hooks,
34618
34741
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
34619
34742
  onPiiRedacted
34620
34743
  });
34621
- 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
+ }
34622
34766
  return result;
34623
34767
  } catch (err) {
34624
34768
  return createErrorResult(
@@ -34629,7 +34773,8 @@ function useChat(options) {
34629
34773
  if (requestIdRef.current === requestId) {
34630
34774
  setIsLoading(false);
34631
34775
  abortControllerRef.current = null;
34632
- if (!succeeded) pendingInferenceRef.current?.cancel();
34776
+ if (detachControllerRef.current === detachController) detachControllerRef.current = null;
34777
+ if (!keepServerStream) pendingInferenceRef.current?.cancel();
34633
34778
  pendingInferenceRef.current = null;
34634
34779
  }
34635
34780
  }
@@ -34648,6 +34793,7 @@ function useChat(options) {
34648
34793
  onToolCallArgumentsDelta,
34649
34794
  onStepFinish,
34650
34795
  resumable,
34796
+ keepAlive,
34651
34797
  onStreamMeta,
34652
34798
  defaultApiType,
34653
34799
  smoothing,
@@ -34660,7 +34806,8 @@ function useChat(options) {
34660
34806
  return {
34661
34807
  isLoading,
34662
34808
  sendMessage,
34663
- stop
34809
+ stop,
34810
+ resumeStream: resumeStream2
34664
34811
  };
34665
34812
  }
34666
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.4",
3
+ "version": "10.6.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",