@tanstack/ai-client 0.34.0 → 0.35.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.
@@ -143,6 +143,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
143
143
  private readonly activeRunIds;
144
144
  /** Latched by `dispose()`; stops any late async callback starting new work. */
145
145
  private disposed;
146
+ /** The error a failed mount hydration set, cleared by the next successful one. */
147
+ private hydrationError;
146
148
  /** Whether a view is currently watching. See `attach` / `detach`. */
147
149
  private tailing;
148
150
  /** Constructor inputs `attach()` needs on every re-attach, not just the first. */
@@ -219,6 +221,19 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
219
221
  * send that starts first owns the client (hydration then backs off).
220
222
  */
221
223
  private hydrateFromServer;
224
+ /**
225
+ * Surface a mount-hydration failure (`persistence: true`) on the observable
226
+ * fields, mirroring `GenerationClient.failHydration`, so "this thread failed
227
+ * to load" is distinguishable from "this thread has no messages" and the app
228
+ * can show an error / offer a retry. A genuine miss — the server having no
229
+ * record for a fresh thread — resolves normally and never reaches here; only a
230
+ * thrown transport / authorize-gate error does.
231
+ *
232
+ * Skipped when the view unmounted (`!tailing`) or a `sendMessage` took
233
+ * ownership while the hydrate GET was in flight, so a live run's state always
234
+ * wins over a stale mount-time failure — same guard as the success path above.
235
+ */
236
+ private failHydration;
222
237
  mountDevtools(): void;
223
238
  private ensureThreadId;
224
239
  /**
@@ -138,10 +138,16 @@ var REJOIN_CONNECT_DEADLINE_MS = 2e3;
138
138
  var REJOIN_REBUILD_TRIGGERS = /* @__PURE__ */ new Set([
139
139
  "TEXT_MESSAGE_START",
140
140
  "TEXT_MESSAGE_CONTENT",
141
+ "REASONING_MESSAGE_CONTENT",
141
142
  "TOOL_CALL_START",
142
143
  "MESSAGES_SNAPSHOT",
143
144
  "SUBAGENT_STARTED"
144
145
  ]);
146
+ function rebuildsAssistantMessage(chunk) {
147
+ if (chunk.type === "REASONING_ENCRYPTED_VALUE") return chunk.subtype === "message" && typeof chunk.encryptedValue === "string" && chunk.encryptedValue.length > 0;
148
+ if (chunk.type === "STEP_FINISHED") return "signature" in chunk && Boolean(chunk.signature);
149
+ return REJOIN_REBUILD_TRIGGERS.has(chunk.type);
150
+ }
145
151
  /** Every subagent card in the messages, nested cards included. */
146
152
  function collectSubagentParts(messages) {
147
153
  const cards = [];
@@ -258,6 +264,8 @@ var ChatClient = class {
258
264
  activeRunIds = /* @__PURE__ */ new Set();
259
265
  /** Latched by `dispose()`; stops any late async callback starting new work. */
260
266
  disposed = false;
267
+ /** The error a failed mount hydration set, cleared by the next successful one. */
268
+ hydrationError;
261
269
  /** Whether a view is currently watching. See `attach` / `detach`. */
262
270
  tailing = false;
263
271
  /** Constructor inputs `attach()` needs on every re-attach, not just the first. */
@@ -556,12 +564,18 @@ var ChatClient = class {
556
564
  const generation = this.historyGeneration;
557
565
  try {
558
566
  result = await hydrate(this.threadId, hydrateOptions);
559
- } catch {
567
+ } catch (cause) {
568
+ if (generation === this.historyGeneration) this.failHydration(cause);
560
569
  return;
561
570
  }
562
571
  if (generation !== this.historyGeneration) return;
563
572
  if (this.disposed || !this.tailing) return;
564
573
  if (this.isLoading || this.abortController) return;
574
+ if (this.hydrationError && this.error === this.hydrationError) {
575
+ this.setError(void 0);
576
+ this.setStatus("ready");
577
+ }
578
+ this.hydrationError = void 0;
565
579
  this.applyHydrationPage(result.page);
566
580
  if (result.messages.length > 0) {
567
581
  const windowMessages = normalizeMessagesDates(result.messages);
@@ -578,6 +592,29 @@ var ChatClient = class {
578
592
  else if (result.activeRun?.runId) this.maybeRejoinInFlight(result.activeRun.runId);
579
593
  })();
580
594
  }
595
+ /**
596
+ * Surface a mount-hydration failure (`persistence: true`) on the observable
597
+ * fields, mirroring `GenerationClient.failHydration`, so "this thread failed
598
+ * to load" is distinguishable from "this thread has no messages" and the app
599
+ * can show an error / offer a retry. A genuine miss — the server having no
600
+ * record for a fresh thread — resolves normally and never reaches here; only a
601
+ * thrown transport / authorize-gate error does.
602
+ *
603
+ * Skipped when the view unmounted (`!tailing`) or a `sendMessage` took
604
+ * ownership while the hydrate GET was in flight, so a live run's state always
605
+ * wins over a stale mount-time failure — same guard as the success path above.
606
+ */
607
+ failHydration(cause) {
608
+ if (this.disposed || !this.tailing) return;
609
+ if (this.isLoading || this.abortController) return;
610
+ const error = cause instanceof Error ? cause : new Error(String(cause));
611
+ if (error instanceof ByokMissingError) this.byok?.request(error.provider, "missing");
612
+ if (error instanceof ByokBlockedError && error.reason === "locked") this.byok?.request(error.provider, "locked");
613
+ this.hydrationError = error;
614
+ this.setStatus("error");
615
+ this.setError(error);
616
+ this.callbacksRef.current.onError(error);
617
+ }
581
618
  mountDevtools() {
582
619
  this.ensureThreadId();
583
620
  if (this.devtoolsMounted) return;
@@ -1033,7 +1070,7 @@ var ChatClient = class {
1033
1070
  attached = true;
1034
1071
  clearTimeout(connectTimer);
1035
1072
  }
1036
- if (!rebuilt && REJOIN_REBUILD_TRIGGERS.has(chunk.type)) {
1073
+ if (!rebuilt && rebuildsAssistantMessage(chunk)) {
1037
1074
  rebuilt = true;
1038
1075
  this.dropTrailingInFlightAssistant();
1039
1076
  }