@anuma/sdk 6.39.1 → 6.39.3

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.
@@ -8741,6 +8741,93 @@ function composeHooks(hooks) {
8741
8741
  };
8742
8742
  }
8743
8743
 
8744
+ // src/lib/chat/streamIdleTimeout.ts
8745
+ var StreamIdleTimeoutError = class extends Error {
8746
+ constructor(idleTimeoutMs) {
8747
+ super(`Stream timed out after ${idleTimeoutMs} ms without activity.`);
8748
+ this.name = "StreamIdleTimeoutError";
8749
+ this.idleTimeoutMs = idleTimeoutMs;
8750
+ }
8751
+ };
8752
+ function withStreamIdleTimeout(transport, idleTimeoutMs) {
8753
+ if (!(idleTimeoutMs > 0) || idleTimeoutMs === Infinity) return transport;
8754
+ return (options) => ({
8755
+ stream: (async function* () {
8756
+ const controller = new AbortController();
8757
+ let timer;
8758
+ let settled = false;
8759
+ let completed = false;
8760
+ let iterator;
8761
+ let interruption;
8762
+ let rejectPending;
8763
+ const disarm = () => {
8764
+ if (timer !== void 0) clearTimeout(timer);
8765
+ timer = void 0;
8766
+ };
8767
+ const interrupt = (error) => {
8768
+ if (interruption) return;
8769
+ interruption = error;
8770
+ rejectPending?.(error);
8771
+ controller.abort();
8772
+ };
8773
+ const arm = () => {
8774
+ if (settled || controller.signal.aborted) return;
8775
+ disarm();
8776
+ timer = setTimeout(() => {
8777
+ interrupt(new StreamIdleTimeoutError(idleTimeoutMs));
8778
+ }, idleTimeoutMs);
8779
+ };
8780
+ const onAbort = () => {
8781
+ controller.abort();
8782
+ };
8783
+ options.signal?.addEventListener("abort", onAbort, { once: true });
8784
+ try {
8785
+ if (options.signal?.aborted) {
8786
+ const error = new Error("Request aborted");
8787
+ error.name = "AbortError";
8788
+ throw error;
8789
+ }
8790
+ arm();
8791
+ iterator = transport({
8792
+ ...options,
8793
+ signal: controller.signal,
8794
+ onActivity: () => {
8795
+ arm();
8796
+ options.onActivity?.();
8797
+ }
8798
+ }).stream[Symbol.asyncIterator]();
8799
+ while (true) {
8800
+ if (interruption) throw interruption;
8801
+ const next = await new Promise((resolve, reject) => {
8802
+ rejectPending = reject;
8803
+ Promise.resolve(iterator.next()).then(resolve, reject);
8804
+ });
8805
+ rejectPending = void 0;
8806
+ if (next.done) {
8807
+ completed = true;
8808
+ break;
8809
+ }
8810
+ arm();
8811
+ yield next.value;
8812
+ }
8813
+ } finally {
8814
+ settled = true;
8815
+ rejectPending = void 0;
8816
+ disarm();
8817
+ options.signal?.removeEventListener("abort", onAbort);
8818
+ if (!completed) {
8819
+ controller.abort();
8820
+ try {
8821
+ void iterator?.return?.().catch(() => {
8822
+ });
8823
+ } catch {
8824
+ }
8825
+ }
8826
+ }
8827
+ })()
8828
+ });
8829
+ }
8830
+
8744
8831
  // src/lib/chat/useChat/utils.ts
8745
8832
  var import_jsonrepair = require("jsonrepair");
8746
8833
  init_logger();
@@ -10931,7 +11018,20 @@ var defaultTransport = (options) => {
10931
11018
  } catch {
10932
11019
  }
10933
11020
  }
10934
- return response;
11021
+ if (!response.body || !options.onActivity) return response;
11022
+ const body = response.body.pipeThrough(
11023
+ new TransformStream({
11024
+ transform(chunk, controller) {
11025
+ if (chunk.byteLength > 0) options.onActivity?.();
11026
+ controller.enqueue(chunk);
11027
+ }
11028
+ })
11029
+ );
11030
+ return new Response(body, {
11031
+ status: response.status,
11032
+ statusText: response.statusText,
11033
+ headers: response.headers
11034
+ });
10935
11035
  };
10936
11036
  return createSseClient({
10937
11037
  method: options.method ?? "POST",
@@ -10988,6 +11088,7 @@ async function runToolLoop(options) {
10988
11088
  conversationId,
10989
11089
  smoothing,
10990
11090
  signal,
11091
+ idleTimeoutMs = 12e4,
10991
11092
  resumable,
10992
11093
  detachSignal,
10993
11094
  onStreamMeta,
@@ -11001,13 +11102,14 @@ async function runToolLoop(options) {
11001
11102
  onStepFinish,
11002
11103
  onRequest,
11003
11104
  onStreamRetry,
11004
- transport: makeStreamingRequest = defaultTransport,
11105
+ transport = defaultTransport,
11005
11106
  preProcessors,
11006
11107
  maxConnectorCalls = 2,
11007
11108
  hooks: hooksOption,
11008
11109
  piiRedaction,
11009
11110
  onPiiRedacted
11010
11111
  } = options;
11112
+ const makeStreamingRequest = withStreamIdleTimeout(transport, idleTimeoutMs);
11011
11113
  const hooks = Array.isArray(hooksOption) ? composeHooks(hooksOption) : hooksOption;
11012
11114
  const runId = generateRunId();
11013
11115
  let messages = options.messages;
@@ -32345,6 +32447,7 @@ function useChat(options) {
32345
32447
  apiType: defaultApiType = "auto",
32346
32448
  smoothing,
32347
32449
  preProcessors,
32450
+ hooks,
32348
32451
  piiRedaction,
32349
32452
  onPiiRedacted
32350
32453
  } = options || {};
@@ -32516,6 +32619,7 @@ function useChat(options) {
32516
32619
  onToolCallArgumentsDelta,
32517
32620
  onStepFinish,
32518
32621
  preProcessors,
32622
+ hooks,
32519
32623
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
32520
32624
  onPiiRedacted
32521
32625
  });
@@ -32553,6 +32657,7 @@ function useChat(options) {
32553
32657
  defaultApiType,
32554
32658
  smoothing,
32555
32659
  preProcessors,
32660
+ hooks,
32556
32661
  resolvedPiiRedaction,
32557
32662
  onPiiRedacted
32558
32663
  ]
@@ -33767,6 +33872,7 @@ function useChatStorage(options) {
33767
33872
  minContentLength = DEFAULT_MIN_CONTENT_LENGTH,
33768
33873
  mcpR2Domain = MCP_R2_DOMAIN,
33769
33874
  preProcessors,
33875
+ hooks,
33770
33876
  smoothing,
33771
33877
  piiRedaction,
33772
33878
  onPiiRedacted,
@@ -34341,6 +34447,7 @@ function useChatStorage(options) {
34341
34447
  onStreamMeta,
34342
34448
  apiType,
34343
34449
  preProcessors,
34450
+ hooks,
34344
34451
  smoothing,
34345
34452
  piiRedaction: resolvedPiiRedaction,
34346
34453
  onPiiRedacted
@@ -2706,6 +2706,127 @@ declare class PiiRedactor {
2706
2706
  clear(): void;
2707
2707
  }
2708
2708
 
2709
+ type LlmTokenUsage = {
2710
+ inputTokens?: number;
2711
+ outputTokens?: number;
2712
+ };
2713
+ type ModelCallStartEvent = {
2714
+ runId: string;
2715
+ stepIndex: number;
2716
+ model: string;
2717
+ messages: Array<LlmapiMessage>;
2718
+ tools: Array<LlmapiChatCompletionTool>;
2719
+ requestBody: Record<string, unknown>;
2720
+ };
2721
+ type ModelCallEndEvent = {
2722
+ runId: string;
2723
+ stepIndex: number;
2724
+ content: string;
2725
+ toolCalls: Array<{
2726
+ id: string;
2727
+ name: string;
2728
+ arguments: string;
2729
+ }>;
2730
+ usage?: LlmTokenUsage;
2731
+ finishReason?: string;
2732
+ error?: string;
2733
+ };
2734
+ type ToolUseStartEvent = {
2735
+ runId: string;
2736
+ stepIndex: number;
2737
+ toolCallId: string;
2738
+ name: string;
2739
+ rawArguments: string;
2740
+ parsedArguments?: Record<string, unknown>;
2741
+ };
2742
+ type ToolUseEndEvent = {
2743
+ runId: string;
2744
+ stepIndex: number;
2745
+ toolCallId: string;
2746
+ name: string;
2747
+ result?: unknown;
2748
+ error?: string;
2749
+ errorType?: "parse" | "timeout" | "execution" | "cancelled";
2750
+ };
2751
+ type RunStartEvent = {
2752
+ runId: string;
2753
+ model: string;
2754
+ messages: Array<LlmapiMessage>;
2755
+ tools: Array<LlmapiChatCompletionTool>;
2756
+ };
2757
+ type RunEndEvent = {
2758
+ runId: string;
2759
+ finalContent: string;
2760
+ /** Number of LLM rounds executed. */
2761
+ totalSteps: number;
2762
+ };
2763
+ type RunErrorEvent = {
2764
+ runId: string;
2765
+ error: string;
2766
+ /**
2767
+ * Only `"model"` today. Tool failures surface on {@link RunHooks.afterToolUse}
2768
+ * with `errorType`, not on `onRunError`, so a tool stage isn't needed yet.
2769
+ * Left as a union to leave room for `"validation"` or `"tool"` later
2770
+ * without a breaking change.
2771
+ */
2772
+ stage: "model";
2773
+ /**
2774
+ * The underlying `Error` when one is available. `RunErrorEvent` predates
2775
+ * this field, so `error` (a string message) is kept for backwards
2776
+ * compatibility; new consumers should prefer `errorObject` when present
2777
+ * for stack traces and `cause`.
2778
+ */
2779
+ errorObject?: Error;
2780
+ };
2781
+ type RunHooks = {
2782
+ /**
2783
+ * Fires once per `runToolLoop` invocation, before any pre-processor or
2784
+ * model call. The `messages` payload is the raw caller-supplied
2785
+ * conversation — pre-processors that enrich it (e.g. web search context)
2786
+ * run afterwards, so `beforeModelCall.messages` reflects the
2787
+ * post-enrichment state.
2788
+ */
2789
+ onRunStart?: (e: RunStartEvent) => Promise<void> | void;
2790
+ /**
2791
+ * Fires exactly once per run on clean completion. Mutually exclusive
2792
+ * with `onRunError` — a run that completes fires `onRunEnd`; a run that
2793
+ * errors or is aborted fires `onRunError`. Consumers can rely on
2794
+ * exactly one terminal hook per `onRunStart`.
2795
+ */
2796
+ onRunEnd?: (e: RunEndEvent) => Promise<void> | void;
2797
+ /**
2798
+ * Fires exactly once per run when the loop errors or is aborted.
2799
+ * `stage` is `"model"` for stream/provider errors and for aborts;
2800
+ * tool execution failures surface as structured `{ error, errorType }`
2801
+ * results on `afterToolUse` rather than firing `onRunError`.
2802
+ * Mutually exclusive with `onRunEnd`.
2803
+ */
2804
+ onRunError?: (e: RunErrorEvent) => Promise<void> | void;
2805
+ beforeModelCall?: (e: ModelCallStartEvent) => Promise<void> | void;
2806
+ afterModelCall?: (e: ModelCallEndEvent) => Promise<void> | void;
2807
+ /**
2808
+ * Fires before each tool the model invoked — including server-side
2809
+ * tools that have no client executor. Today this is observe-only.
2810
+ * TODO(follow-up PR): allow returning { args?, abort?: { reason } } to
2811
+ * mutate arguments or short-circuit the tool call.
2812
+ *
2813
+ * Pairing with {@link afterToolUse} is asymmetric: server-side tools
2814
+ * routed via `onToolCall` get `beforeToolUse` but no `afterToolUse`,
2815
+ * because they don't execute in-process. Tools blocked by a failed
2816
+ * dependency or a dependency cycle DO get `afterToolUse` with
2817
+ * `errorType: "execution"` — consumers tallying matched pairs should
2818
+ * filter on executor-backed tools to avoid spurious imbalances.
2819
+ */
2820
+ beforeToolUse?: (e: ToolUseStartEvent) => Promise<void> | void;
2821
+ /**
2822
+ * Fires after each executor-backed tool finishes (success or failure)
2823
+ * and after each dependency-blocked tool is skipped (`errorType:
2824
+ * "execution"`). Does NOT fire for server-side tools routed via
2825
+ * `onToolCall`. See {@link beforeToolUse} for the pairing contract.
2826
+ */
2827
+ afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
2828
+ };
2829
+
2709
2830
  /**
2710
2831
  * Configuration for stream output smoothing.
2711
2832
  *
@@ -2900,6 +3021,8 @@ type BaseSendMessageArgs = ResponsesApiOptions & {
2900
3021
  type BaseUseChatOptions = {
2901
3022
  getToken?: () => Promise<string | null>;
2902
3023
  baseUrl?: string;
3024
+ /** Run lifecycle hooks for telemetry and tracing, for example from `createMetricsHooks` */
3025
+ hooks?: RunHooks | RunHooks[];
2903
3026
  /**
2904
3027
  * Callback function to be called when a new data chunk is received.
2905
3028
  */
@@ -3124,12 +3247,10 @@ type StreamingTransportOptions = {
3124
3247
  inferenceId: string;
3125
3248
  }) => void;
3126
3249
  /**
3127
- * Fires whenever bytes arrive on the wire — data frames AND the keep-alive
3128
- * comment lines (`: ...`) the SSE parser otherwise discards. A pure liveness
3129
- * signal: a consumer running an idle watchdog uses it to tell a slow-but-alive
3130
- * stream (the server still heart-beating through a long reasoning silence)
3131
- * apart from a dead connection (no bytes at all). Not every transport emits it
3132
- * — the xhr transport does; treat its absence as "no extra liveness info".
3250
+ * Fires when the transport receives bytes, including SSE keep-alive comments.
3251
+ * Consumers can use this callback to reset the idle timeout.
3252
+ * The fetch and XHR transports emit it.
3253
+ * Custom transports can omit it if they cannot report byte activity.
3133
3254
  */
3134
3255
  onActivity?: () => void;
3135
3256
  };
@@ -8691,6 +8812,8 @@ interface CreateConversationOptions {
8691
8812
  * @inline
8692
8813
  */
8693
8814
  interface BaseUseChatStorageOptions {
8815
+ /** Run lifecycle hooks for telemetry and tracing, for example from `createMetricsHooks` */
8816
+ hooks?: RunHooks | RunHooks[];
8694
8817
  /** WatermelonDB database instance for storing conversations and messages */
8695
8818
  database: Database;
8696
8819
  /** Route memory reads, writes, recall and the memory tools through this store instead of the database's vault; folders are unsupported. With `piiRedaction` on, build the store with `embeddingOptions.maskInput` (e.g. `(text) => redactor.maskText(text)` with any `PiiRedactor`) or its memory calls throw. */
@@ -2706,6 +2706,127 @@ declare class PiiRedactor {
2706
2706
  clear(): void;
2707
2707
  }
2708
2708
 
2709
+ type LlmTokenUsage = {
2710
+ inputTokens?: number;
2711
+ outputTokens?: number;
2712
+ };
2713
+ type ModelCallStartEvent = {
2714
+ runId: string;
2715
+ stepIndex: number;
2716
+ model: string;
2717
+ messages: Array<LlmapiMessage>;
2718
+ tools: Array<LlmapiChatCompletionTool>;
2719
+ requestBody: Record<string, unknown>;
2720
+ };
2721
+ type ModelCallEndEvent = {
2722
+ runId: string;
2723
+ stepIndex: number;
2724
+ content: string;
2725
+ toolCalls: Array<{
2726
+ id: string;
2727
+ name: string;
2728
+ arguments: string;
2729
+ }>;
2730
+ usage?: LlmTokenUsage;
2731
+ finishReason?: string;
2732
+ error?: string;
2733
+ };
2734
+ type ToolUseStartEvent = {
2735
+ runId: string;
2736
+ stepIndex: number;
2737
+ toolCallId: string;
2738
+ name: string;
2739
+ rawArguments: string;
2740
+ parsedArguments?: Record<string, unknown>;
2741
+ };
2742
+ type ToolUseEndEvent = {
2743
+ runId: string;
2744
+ stepIndex: number;
2745
+ toolCallId: string;
2746
+ name: string;
2747
+ result?: unknown;
2748
+ error?: string;
2749
+ errorType?: "parse" | "timeout" | "execution" | "cancelled";
2750
+ };
2751
+ type RunStartEvent = {
2752
+ runId: string;
2753
+ model: string;
2754
+ messages: Array<LlmapiMessage>;
2755
+ tools: Array<LlmapiChatCompletionTool>;
2756
+ };
2757
+ type RunEndEvent = {
2758
+ runId: string;
2759
+ finalContent: string;
2760
+ /** Number of LLM rounds executed. */
2761
+ totalSteps: number;
2762
+ };
2763
+ type RunErrorEvent = {
2764
+ runId: string;
2765
+ error: string;
2766
+ /**
2767
+ * Only `"model"` today. Tool failures surface on {@link RunHooks.afterToolUse}
2768
+ * with `errorType`, not on `onRunError`, so a tool stage isn't needed yet.
2769
+ * Left as a union to leave room for `"validation"` or `"tool"` later
2770
+ * without a breaking change.
2771
+ */
2772
+ stage: "model";
2773
+ /**
2774
+ * The underlying `Error` when one is available. `RunErrorEvent` predates
2775
+ * this field, so `error` (a string message) is kept for backwards
2776
+ * compatibility; new consumers should prefer `errorObject` when present
2777
+ * for stack traces and `cause`.
2778
+ */
2779
+ errorObject?: Error;
2780
+ };
2781
+ type RunHooks = {
2782
+ /**
2783
+ * Fires once per `runToolLoop` invocation, before any pre-processor or
2784
+ * model call. The `messages` payload is the raw caller-supplied
2785
+ * conversation — pre-processors that enrich it (e.g. web search context)
2786
+ * run afterwards, so `beforeModelCall.messages` reflects the
2787
+ * post-enrichment state.
2788
+ */
2789
+ onRunStart?: (e: RunStartEvent) => Promise<void> | void;
2790
+ /**
2791
+ * Fires exactly once per run on clean completion. Mutually exclusive
2792
+ * with `onRunError` — a run that completes fires `onRunEnd`; a run that
2793
+ * errors or is aborted fires `onRunError`. Consumers can rely on
2794
+ * exactly one terminal hook per `onRunStart`.
2795
+ */
2796
+ onRunEnd?: (e: RunEndEvent) => Promise<void> | void;
2797
+ /**
2798
+ * Fires exactly once per run when the loop errors or is aborted.
2799
+ * `stage` is `"model"` for stream/provider errors and for aborts;
2800
+ * tool execution failures surface as structured `{ error, errorType }`
2801
+ * results on `afterToolUse` rather than firing `onRunError`.
2802
+ * Mutually exclusive with `onRunEnd`.
2803
+ */
2804
+ onRunError?: (e: RunErrorEvent) => Promise<void> | void;
2805
+ beforeModelCall?: (e: ModelCallStartEvent) => Promise<void> | void;
2806
+ afterModelCall?: (e: ModelCallEndEvent) => Promise<void> | void;
2807
+ /**
2808
+ * Fires before each tool the model invoked — including server-side
2809
+ * tools that have no client executor. Today this is observe-only.
2810
+ * TODO(follow-up PR): allow returning { args?, abort?: { reason } } to
2811
+ * mutate arguments or short-circuit the tool call.
2812
+ *
2813
+ * Pairing with {@link afterToolUse} is asymmetric: server-side tools
2814
+ * routed via `onToolCall` get `beforeToolUse` but no `afterToolUse`,
2815
+ * because they don't execute in-process. Tools blocked by a failed
2816
+ * dependency or a dependency cycle DO get `afterToolUse` with
2817
+ * `errorType: "execution"` — consumers tallying matched pairs should
2818
+ * filter on executor-backed tools to avoid spurious imbalances.
2819
+ */
2820
+ beforeToolUse?: (e: ToolUseStartEvent) => Promise<void> | void;
2821
+ /**
2822
+ * Fires after each executor-backed tool finishes (success or failure)
2823
+ * and after each dependency-blocked tool is skipped (`errorType:
2824
+ * "execution"`). Does NOT fire for server-side tools routed via
2825
+ * `onToolCall`. See {@link beforeToolUse} for the pairing contract.
2826
+ */
2827
+ afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
2828
+ };
2829
+
2709
2830
  /**
2710
2831
  * Configuration for stream output smoothing.
2711
2832
  *
@@ -2900,6 +3021,8 @@ type BaseSendMessageArgs = ResponsesApiOptions & {
2900
3021
  type BaseUseChatOptions = {
2901
3022
  getToken?: () => Promise<string | null>;
2902
3023
  baseUrl?: string;
3024
+ /** Run lifecycle hooks for telemetry and tracing, for example from `createMetricsHooks` */
3025
+ hooks?: RunHooks | RunHooks[];
2903
3026
  /**
2904
3027
  * Callback function to be called when a new data chunk is received.
2905
3028
  */
@@ -3124,12 +3247,10 @@ type StreamingTransportOptions = {
3124
3247
  inferenceId: string;
3125
3248
  }) => void;
3126
3249
  /**
3127
- * Fires whenever bytes arrive on the wire — data frames AND the keep-alive
3128
- * comment lines (`: ...`) the SSE parser otherwise discards. A pure liveness
3129
- * signal: a consumer running an idle watchdog uses it to tell a slow-but-alive
3130
- * stream (the server still heart-beating through a long reasoning silence)
3131
- * apart from a dead connection (no bytes at all). Not every transport emits it
3132
- * — the xhr transport does; treat its absence as "no extra liveness info".
3250
+ * Fires when the transport receives bytes, including SSE keep-alive comments.
3251
+ * Consumers can use this callback to reset the idle timeout.
3252
+ * The fetch and XHR transports emit it.
3253
+ * Custom transports can omit it if they cannot report byte activity.
3133
3254
  */
3134
3255
  onActivity?: () => void;
3135
3256
  };
@@ -8691,6 +8812,8 @@ interface CreateConversationOptions {
8691
8812
  * @inline
8692
8813
  */
8693
8814
  interface BaseUseChatStorageOptions {
8815
+ /** Run lifecycle hooks for telemetry and tracing, for example from `createMetricsHooks` */
8816
+ hooks?: RunHooks | RunHooks[];
8694
8817
  /** WatermelonDB database instance for storing conversations and messages */
8695
8818
  database: Database;
8696
8819
  /** Route memory reads, writes, recall and the memory tools through this store instead of the database's vault; folders are unsupported. With `piiRedaction` on, build the store with `embeddingOptions.maskInput` (e.g. `(text) => redactor.maskText(text)` with any `PiiRedactor`) or its memory calls throw. */
@@ -6889,6 +6889,93 @@ function composeHooks(hooks) {
6889
6889
  };
6890
6890
  }
6891
6891
 
6892
+ // src/lib/chat/streamIdleTimeout.ts
6893
+ var StreamIdleTimeoutError = class extends Error {
6894
+ constructor(idleTimeoutMs) {
6895
+ super(`Stream timed out after ${idleTimeoutMs} ms without activity.`);
6896
+ this.name = "StreamIdleTimeoutError";
6897
+ this.idleTimeoutMs = idleTimeoutMs;
6898
+ }
6899
+ };
6900
+ function withStreamIdleTimeout(transport, idleTimeoutMs) {
6901
+ if (!(idleTimeoutMs > 0) || idleTimeoutMs === Infinity) return transport;
6902
+ return (options) => ({
6903
+ stream: (async function* () {
6904
+ const controller = new AbortController();
6905
+ let timer;
6906
+ let settled = false;
6907
+ let completed = false;
6908
+ let iterator;
6909
+ let interruption;
6910
+ let rejectPending;
6911
+ const disarm = () => {
6912
+ if (timer !== void 0) clearTimeout(timer);
6913
+ timer = void 0;
6914
+ };
6915
+ const interrupt = (error) => {
6916
+ if (interruption) return;
6917
+ interruption = error;
6918
+ rejectPending?.(error);
6919
+ controller.abort();
6920
+ };
6921
+ const arm = () => {
6922
+ if (settled || controller.signal.aborted) return;
6923
+ disarm();
6924
+ timer = setTimeout(() => {
6925
+ interrupt(new StreamIdleTimeoutError(idleTimeoutMs));
6926
+ }, idleTimeoutMs);
6927
+ };
6928
+ const onAbort = () => {
6929
+ controller.abort();
6930
+ };
6931
+ options.signal?.addEventListener("abort", onAbort, { once: true });
6932
+ try {
6933
+ if (options.signal?.aborted) {
6934
+ const error = new Error("Request aborted");
6935
+ error.name = "AbortError";
6936
+ throw error;
6937
+ }
6938
+ arm();
6939
+ iterator = transport({
6940
+ ...options,
6941
+ signal: controller.signal,
6942
+ onActivity: () => {
6943
+ arm();
6944
+ options.onActivity?.();
6945
+ }
6946
+ }).stream[Symbol.asyncIterator]();
6947
+ while (true) {
6948
+ if (interruption) throw interruption;
6949
+ const next = await new Promise((resolve, reject) => {
6950
+ rejectPending = reject;
6951
+ Promise.resolve(iterator.next()).then(resolve, reject);
6952
+ });
6953
+ rejectPending = void 0;
6954
+ if (next.done) {
6955
+ completed = true;
6956
+ break;
6957
+ }
6958
+ arm();
6959
+ yield next.value;
6960
+ }
6961
+ } finally {
6962
+ settled = true;
6963
+ rejectPending = void 0;
6964
+ disarm();
6965
+ options.signal?.removeEventListener("abort", onAbort);
6966
+ if (!completed) {
6967
+ controller.abort();
6968
+ try {
6969
+ void iterator?.return?.().catch(() => {
6970
+ });
6971
+ } catch {
6972
+ }
6973
+ }
6974
+ }
6975
+ })()
6976
+ });
6977
+ }
6978
+
6892
6979
  // src/lib/chat/useChat/utils.ts
6893
6980
  import { jsonrepair } from "jsonrepair";
6894
6981
  function parseToolArguments(raw) {
@@ -9078,7 +9165,20 @@ var defaultTransport = (options) => {
9078
9165
  } catch {
9079
9166
  }
9080
9167
  }
9081
- return response;
9168
+ if (!response.body || !options.onActivity) return response;
9169
+ const body = response.body.pipeThrough(
9170
+ new TransformStream({
9171
+ transform(chunk, controller) {
9172
+ if (chunk.byteLength > 0) options.onActivity?.();
9173
+ controller.enqueue(chunk);
9174
+ }
9175
+ })
9176
+ );
9177
+ return new Response(body, {
9178
+ status: response.status,
9179
+ statusText: response.statusText,
9180
+ headers: response.headers
9181
+ });
9082
9182
  };
9083
9183
  return createSseClient({
9084
9184
  method: options.method ?? "POST",
@@ -9135,6 +9235,7 @@ async function runToolLoop(options) {
9135
9235
  conversationId,
9136
9236
  smoothing,
9137
9237
  signal,
9238
+ idleTimeoutMs = 12e4,
9138
9239
  resumable,
9139
9240
  detachSignal,
9140
9241
  onStreamMeta,
@@ -9148,13 +9249,14 @@ async function runToolLoop(options) {
9148
9249
  onStepFinish,
9149
9250
  onRequest,
9150
9251
  onStreamRetry,
9151
- transport: makeStreamingRequest = defaultTransport,
9252
+ transport = defaultTransport,
9152
9253
  preProcessors,
9153
9254
  maxConnectorCalls = 2,
9154
9255
  hooks: hooksOption,
9155
9256
  piiRedaction,
9156
9257
  onPiiRedacted
9157
9258
  } = options;
9259
+ const makeStreamingRequest = withStreamIdleTimeout(transport, idleTimeoutMs);
9158
9260
  const hooks = Array.isArray(hooksOption) ? composeHooks(hooksOption) : hooksOption;
9159
9261
  const runId = generateRunId();
9160
9262
  let messages = options.messages;
@@ -30435,6 +30537,7 @@ function useChat(options) {
30435
30537
  apiType: defaultApiType = "auto",
30436
30538
  smoothing,
30437
30539
  preProcessors,
30540
+ hooks,
30438
30541
  piiRedaction,
30439
30542
  onPiiRedacted
30440
30543
  } = options || {};
@@ -30606,6 +30709,7 @@ function useChat(options) {
30606
30709
  onToolCallArgumentsDelta,
30607
30710
  onStepFinish,
30608
30711
  preProcessors,
30712
+ hooks,
30609
30713
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
30610
30714
  onPiiRedacted
30611
30715
  });
@@ -30643,6 +30747,7 @@ function useChat(options) {
30643
30747
  defaultApiType,
30644
30748
  smoothing,
30645
30749
  preProcessors,
30750
+ hooks,
30646
30751
  resolvedPiiRedaction,
30647
30752
  onPiiRedacted
30648
30753
  ]
@@ -31847,6 +31952,7 @@ function useChatStorage(options) {
31847
31952
  minContentLength = DEFAULT_MIN_CONTENT_LENGTH,
31848
31953
  mcpR2Domain = MCP_R2_DOMAIN,
31849
31954
  preProcessors,
31955
+ hooks,
31850
31956
  smoothing,
31851
31957
  piiRedaction,
31852
31958
  onPiiRedacted,
@@ -32421,6 +32527,7 @@ function useChatStorage(options) {
32421
32527
  onStreamMeta,
32422
32528
  apiType,
32423
32529
  preProcessors,
32530
+ hooks,
32424
32531
  smoothing,
32425
32532
  piiRedaction: resolvedPiiRedaction,
32426
32533
  onPiiRedacted