@anuma/sdk 10.5.4 → 10.6.1

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.
@@ -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 };
@@ -20356,6 +20374,52 @@ async function getDecayCandidatesRawOp(ctx) {
20356
20374
  trustTier: raw.trust_tier ?? null
20357
20375
  }));
20358
20376
  }
20377
+ async function applyDecayTransitionsOp(ctx, candidates, opts) {
20378
+ assertVaultScopeForSweep(ctx);
20379
+ const byId = new Map(candidates.map((candidate) => [candidate.uniqueId, candidate]));
20380
+ const ids = Array.from(byId.keys());
20381
+ const changed = /* @__PURE__ */ new Set();
20382
+ const CHUNK = 500;
20383
+ for (let i = 0; i < ids.length; i += CHUNK) {
20384
+ let applied = [];
20385
+ try {
20386
+ await ctx.database.write(async () => {
20387
+ await assertCanWrite2(ctx);
20388
+ const records = await ctx.vaultMemoryCollection.query(
20389
+ import_watermelondb24.Q.where("id", import_watermelondb24.Q.oneOf(ids.slice(i, i + CHUNK))),
20390
+ ...ctx.userId !== void 0 ? [import_watermelondb24.Q.where("user_id", ctx.userId)] : []
20391
+ ).fetch();
20392
+ const eligible = records.filter((record2) => {
20393
+ if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
20394
+ if (opts.transition === "delete") {
20395
+ return record2.archivedAt !== null && opts.now - record2.archivedAt > opts.hardDeleteWindowMs;
20396
+ }
20397
+ const candidate = byId.get(record2.id);
20398
+ return record2.archivedAt === null && record2.updatedAt.getTime() === candidate.updatedAt && (record2.lastObservedAt ?? null) === (candidate.lastObservedAt ?? null);
20399
+ });
20400
+ const prepared = eligible.map(
20401
+ (record2) => record2.prepareUpdate((row) => {
20402
+ if (opts.transition === "archive") row._setRaw("archived_at", opts.now);
20403
+ else row._setRaw("is_deleted", true);
20404
+ })
20405
+ );
20406
+ if (prepared.length > 0) await ctx.database.batch(prepared);
20407
+ applied = eligible.map((record2) => record2.id);
20408
+ });
20409
+ } catch {
20410
+ continue;
20411
+ }
20412
+ for (const id2 of applied) changed.add(id2);
20413
+ if (opts.transition === "delete" && ctx.entityCtx) {
20414
+ try {
20415
+ await unlinkMemoryEntitiesOp(ctx.entityCtx, applied);
20416
+ } catch {
20417
+ continue;
20418
+ }
20419
+ }
20420
+ }
20421
+ return changed;
20422
+ }
20359
20423
  async function archiveVaultMemoryOp(ctx, id2, opts) {
20360
20424
  try {
20361
20425
  const record2 = await ctx.vaultMemoryCollection.find(id2);
@@ -25426,25 +25490,8 @@ function isBorderline(input) {
25426
25490
  function vaultDecayBackend(vaultCtx) {
25427
25491
  return {
25428
25492
  candidates: () => getDecayCandidatesRawOp(vaultCtx),
25429
- archive: async (rows, now) => {
25430
- const archived = /* @__PURE__ */ new Set();
25431
- for (const c of rows) {
25432
- const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
25433
- now,
25434
- expectedUpdatedAt: c.updatedAt,
25435
- expectedLastObservedAt: c.lastObservedAt ?? null
25436
- });
25437
- if (ok) archived.add(c.uniqueId);
25438
- }
25439
- return archived;
25440
- },
25441
- hardDelete: async (rows, deleteOptions) => {
25442
- const deleted = /* @__PURE__ */ new Set();
25443
- for (const c of rows) {
25444
- if (await hardDeleteDecayedOp(vaultCtx, c.uniqueId, deleteOptions)) deleted.add(c.uniqueId);
25445
- }
25446
- return deleted;
25447
- }
25493
+ archive: (rows, now) => applyDecayTransitionsOp(vaultCtx, rows, { transition: "archive", now }),
25494
+ hardDelete: (rows, deleteOptions) => applyDecayTransitionsOp(vaultCtx, rows, { transition: "delete", ...deleteOptions })
25448
25495
  };
25449
25496
  }
25450
25497
  function createDecaySweeper(options) {
@@ -27472,6 +27519,7 @@ function createLocalMemoryStore(options) {
27472
27519
  "trust_tier",
27473
27520
  "superseded_by",
27474
27521
  "visibility",
27522
+ "twin_opt_in",
27475
27523
  "published_at",
27476
27524
  "geohash",
27477
27525
  "topics",
@@ -28663,6 +28711,7 @@ function subscriptionState({ memory, version }) {
28663
28711
  memory.event_time_kind,
28664
28712
  memory.trust_tier,
28665
28713
  memory.visibility,
28714
+ memory.twin_opt_in,
28666
28715
  memory.published_at,
28667
28716
  memory.geohash,
28668
28717
  memory.topics,
@@ -36366,6 +36415,7 @@ function useChat(options) {
36366
36415
  onToolCallArgumentsDelta,
36367
36416
  onStepFinish,
36368
36417
  resumable,
36418
+ background = false,
36369
36419
  onStreamMeta,
36370
36420
  apiType: defaultApiType = "auto",
36371
36421
  smoothing,
@@ -36374,12 +36424,12 @@ function useChat(options) {
36374
36424
  piiRedaction,
36375
36425
  onPiiRedacted
36376
36426
  } = options || {};
36427
+ const keepAlive = resumable === true && background;
36377
36428
  const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
36378
36429
  const abortControllerRef = (0, import_react4.useRef)(null);
36379
36430
  const requestIdRef = (0, import_react4.useRef)(0);
36380
- const pendingInferenceRef = (0, import_react4.useRef)(
36381
- null
36382
- );
36431
+ const detachControllerRef = (0, import_react4.useRef)(null);
36432
+ const pendingInferenceRef = (0, import_react4.useRef)(null);
36383
36433
  const cancelInference = (0, import_react4.useCallback)(
36384
36434
  (inferenceId, requestToken) => {
36385
36435
  void (async () => {
@@ -36407,7 +36457,95 @@ function useChat(options) {
36407
36457
  abortControllerRef.current = null;
36408
36458
  }
36409
36459
  }, []);
36410
- (0, import_react4.useEffect)(() => stop, [stop]);
36460
+ const keepAliveRef = (0, import_react4.useRef)(keepAlive);
36461
+ keepAliveRef.current = keepAlive;
36462
+ (0, import_react4.useEffect)(
36463
+ () => () => {
36464
+ if (keepAliveRef.current) {
36465
+ if (detachControllerRef.current) {
36466
+ detachControllerRef.current.abort();
36467
+ return;
36468
+ }
36469
+ pendingInferenceRef.current = null;
36470
+ abortControllerRef.current?.abort();
36471
+ abortControllerRef.current = null;
36472
+ return;
36473
+ }
36474
+ stop();
36475
+ },
36476
+ [stop]
36477
+ );
36478
+ const resumeStream2 = (0, import_react4.useCallback)(
36479
+ async (handle, opts) => {
36480
+ const getterValidation = validateTokenGetter(getToken);
36481
+ if (!getterValidation.valid) {
36482
+ return { data: null, error: getterValidation.message, interrupted: false };
36483
+ }
36484
+ const { headless, ...resumeOpts } = opts ?? {};
36485
+ if (!headless && abortControllerRef.current) {
36486
+ return { data: null, error: "A request is already in progress", interrupted: false };
36487
+ }
36488
+ const abortController = new AbortController();
36489
+ const requestId = headless ? null : ++requestIdRef.current;
36490
+ if (!headless) {
36491
+ abortControllerRef.current = abortController;
36492
+ pendingInferenceRef.current = {
36493
+ id: handle.inferenceId,
36494
+ round: -1,
36495
+ cancel: () => cancelInference(handle.inferenceId),
36496
+ handle
36497
+ };
36498
+ setIsLoading(true);
36499
+ }
36500
+ const ownsForeground = () => requestId !== null && requestIdRef.current === requestId;
36501
+ try {
36502
+ let token;
36503
+ try {
36504
+ token = await getToken();
36505
+ } catch (err) {
36506
+ const error = err instanceof Error ? err : new Error(String(err));
36507
+ if (!headless) onError?.(error);
36508
+ return { data: null, error: error.message, interrupted: false };
36509
+ }
36510
+ const tokenValidation = validateToken(token);
36511
+ if (!tokenValidation.valid) {
36512
+ return { data: null, error: tokenValidation.message, interrupted: false };
36513
+ }
36514
+ return await resumeStream({
36515
+ handle,
36516
+ token,
36517
+ baseUrl,
36518
+ smoothing,
36519
+ signal: abortController.signal,
36520
+ piiRedactor: typeof resolvedPiiRedaction === "object" ? resolvedPiiRedaction : void 0,
36521
+ ...headless ? {} : {
36522
+ onData: (chunk) => globalOnData?.(chunk),
36523
+ onThinking: (chunk) => globalOnThinking?.(chunk),
36524
+ onFinish,
36525
+ onError
36526
+ },
36527
+ ...resumeOpts
36528
+ });
36529
+ } finally {
36530
+ if (ownsForeground()) {
36531
+ setIsLoading(false);
36532
+ if (abortControllerRef.current === abortController) abortControllerRef.current = null;
36533
+ if (pendingInferenceRef.current?.round === -1) pendingInferenceRef.current = null;
36534
+ }
36535
+ }
36536
+ },
36537
+ [
36538
+ getToken,
36539
+ baseUrl,
36540
+ smoothing,
36541
+ cancelInference,
36542
+ resolvedPiiRedaction,
36543
+ globalOnData,
36544
+ globalOnThinking,
36545
+ onFinish,
36546
+ onError
36547
+ ]
36548
+ );
36411
36549
  const sendMessage = (0, import_react4.useCallback)(
36412
36550
  async ({
36413
36551
  messages,
@@ -36435,9 +36573,12 @@ function useChat(options) {
36435
36573
  stop();
36436
36574
  const abortController = new AbortController();
36437
36575
  abortControllerRef.current = abortController;
36576
+ const detachController = keepAlive ? new AbortController() : null;
36577
+ detachControllerRef.current = detachController;
36438
36578
  const requestId = ++requestIdRef.current;
36439
36579
  setIsLoading(true);
36440
- let succeeded = false;
36580
+ let keepServerStream = false;
36581
+ const deferredErrors = [];
36441
36582
  try {
36442
36583
  const tokenGetterValidation = validateTokenGetter(getToken);
36443
36584
  if (!tokenGetterValidation.valid) {
@@ -36501,6 +36642,7 @@ function useChat(options) {
36501
36642
  apiType: requestApiType ?? defaultApiType,
36502
36643
  endpointOverride,
36503
36644
  resumable,
36645
+ detachSignal: detachController?.signal,
36504
36646
  onStreamMeta: resumable || onStreamMeta ? (meta) => {
36505
36647
  if (requestId !== requestIdRef.current || abortController.signal.aborted) {
36506
36648
  if (resumable) cancelInference(meta.inferenceId, token);
@@ -36509,12 +36651,19 @@ function useChat(options) {
36509
36651
  const previous = pendingInferenceRef.current;
36510
36652
  if (previous && previous.round === meta.round && previous.id !== meta.inferenceId)
36511
36653
  previous.cancel();
36654
+ const handle2 = {
36655
+ inferenceId: meta.inferenceId,
36656
+ apiType: resolveApiType(requestApiType ?? defaultApiType, model),
36657
+ model,
36658
+ conversationId
36659
+ };
36512
36660
  pendingInferenceRef.current = {
36513
36661
  id: meta.inferenceId,
36514
36662
  round: meta.round,
36515
- cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0
36663
+ cancel: resumable ? () => cancelInference(meta.inferenceId, token) : () => void 0,
36664
+ handle: handle2
36516
36665
  };
36517
- onStreamMeta?.(meta);
36666
+ onStreamMeta?.({ ...handle2, round: meta.round });
36518
36667
  } : void 0,
36519
36668
  temperature,
36520
36669
  maxOutputTokens,
@@ -36536,17 +36685,44 @@ function useChat(options) {
36536
36685
  if (globalOnThinking) globalOnThinking(chunk);
36537
36686
  },
36538
36687
  onFinish,
36539
- onError,
36688
+ onError: keepAlive ? (error) => deferredErrors.push(error) : onError,
36540
36689
  onToolCall,
36541
36690
  onServerToolCall,
36542
36691
  onToolCallArgumentsDelta,
36543
- onStepFinish,
36692
+ onStepFinish: keepAlive || onStepFinish ? (event) => {
36693
+ const pending2 = pendingInferenceRef.current;
36694
+ if (keepAlive && pending2 && requestIdRef.current === requestId) {
36695
+ pendingInferenceRef.current = { ...pending2, settled: true };
36696
+ }
36697
+ onStepFinish?.(event);
36698
+ } : void 0,
36544
36699
  preProcessors,
36545
36700
  hooks,
36546
36701
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
36547
36702
  onPiiRedacted
36548
36703
  });
36549
- succeeded = result.error === null;
36704
+ const pending = pendingInferenceRef.current;
36705
+ const handle = pending && !pending.settled ? pending.handle : null;
36706
+ const detach = result.error !== null && keepAlive && handle && result.error !== "Request aborted";
36707
+ if (!detach) deferredErrors.forEach((error) => onError?.(error));
36708
+ if (result.error === null) {
36709
+ keepServerStream = true;
36710
+ return result;
36711
+ }
36712
+ if (detach) {
36713
+ keepServerStream = true;
36714
+ return {
36715
+ data: result.data,
36716
+ error: "Request detached",
36717
+ detached: true,
36718
+ resume: handle,
36719
+ toolsChecksum: result.toolsChecksum
36720
+ };
36721
+ }
36722
+ if ("detached" in result && result.detached) {
36723
+ keepServerStream = true;
36724
+ return { ...result, resume: null };
36725
+ }
36550
36726
  return result;
36551
36727
  } catch (err) {
36552
36728
  return createErrorResult(
@@ -36557,7 +36733,8 @@ function useChat(options) {
36557
36733
  if (requestIdRef.current === requestId) {
36558
36734
  setIsLoading(false);
36559
36735
  abortControllerRef.current = null;
36560
- if (!succeeded) pendingInferenceRef.current?.cancel();
36736
+ if (detachControllerRef.current === detachController) detachControllerRef.current = null;
36737
+ if (!keepServerStream) pendingInferenceRef.current?.cancel();
36561
36738
  pendingInferenceRef.current = null;
36562
36739
  }
36563
36740
  }
@@ -36576,6 +36753,7 @@ function useChat(options) {
36576
36753
  onToolCallArgumentsDelta,
36577
36754
  onStepFinish,
36578
36755
  resumable,
36756
+ keepAlive,
36579
36757
  onStreamMeta,
36580
36758
  defaultApiType,
36581
36759
  smoothing,
@@ -36588,7 +36766,8 @@ function useChat(options) {
36588
36766
  return {
36589
36767
  isLoading,
36590
36768
  sendMessage,
36591
- stop
36769
+ stop,
36770
+ resumeStream: resumeStream2
36592
36771
  };
36593
36772
  }
36594
36773
 
@@ -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.