@dudousxd/nestjs-agent-react 0.21.0 → 0.22.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.
package/dist/index.js CHANGED
@@ -115,11 +115,6 @@ var AgentClient = class {
115
115
  forkFromMessage(threadId, messageId) {
116
116
  return this.request("POST", `/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
117
117
  }
118
- renameThread(id, title) {
119
- return this.updateThread(id, {
120
- title
121
- });
122
- }
123
118
  /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
124
119
  updateThread(id, patch) {
125
120
  return this.request("PATCH", `/threads/${encodeURIComponent(id)}`, patch);
@@ -212,9 +207,6 @@ var AgentClient = class {
212
207
  getQuota() {
213
208
  return this.request("GET", "/quota");
214
209
  }
215
- getQuotaToday() {
216
- return this.request("GET", "/quota/today");
217
- }
218
210
  cancelStream(runId) {
219
211
  return this.request("POST", `/chat/${encodeURIComponent(runId)}/cancel`);
220
212
  }
@@ -626,13 +618,14 @@ var AgentChatTransport = class {
626
618
  function ensureStarted() {
627
619
  if (started) return;
628
620
  started = true;
629
- controller.enqueue(streamRunId !== void 0 ? {
621
+ controller.enqueue({
630
622
  type: "start",
631
623
  messageMetadata: {
632
- runId: streamRunId
624
+ ...streamRunId !== void 0 ? {
625
+ runId: streamRunId
626
+ } : {},
627
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
633
628
  }
634
- } : {
635
- type: "start"
636
629
  });
637
630
  }
638
631
  __name(ensureStarted, "ensureStarted");
@@ -3662,14 +3655,18 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3662
3655
  const [picks, setPicks] = useState10(() => /* @__PURE__ */ new Map());
3663
3656
  const [settling, setSettling] = useState10(() => /* @__PURE__ */ new Map());
3664
3657
  const [settleErrors, setSettleErrors] = useState10(() => /* @__PURE__ */ new Map());
3658
+ const backend = useAgentBackend();
3659
+ const handlers = settleHandlers(options, backend);
3665
3660
  const latest = useRef9({
3666
3661
  options,
3662
+ handlers,
3667
3663
  drafts,
3668
3664
  picks,
3669
3665
  messages
3670
3666
  });
3671
3667
  latest.current = {
3672
3668
  options,
3669
+ handlers,
3673
3670
  drafts,
3674
3671
  picks,
3675
3672
  messages
@@ -3729,34 +3726,46 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3729
3726
  }, []);
3730
3727
  const answer = useCallback8((toolCallId) => {
3731
3728
  const current = latest.current;
3732
- const onAnswer = current.options.onAnswer;
3729
+ const onAnswer = current.handlers.onAnswer;
3733
3730
  if (!onAnswer) {
3734
3731
  return;
3735
3732
  }
3736
- settle(toolCallId, "answer", () => onAnswer(toolCallId, answersFor(current.picks, toolCallId)));
3733
+ settle(toolCallId, "answer", () => onAnswer({
3734
+ toolCallId,
3735
+ answers: answersFor(current.picks, toolCallId)
3736
+ }));
3737
3737
  }, [
3738
3738
  settle
3739
3739
  ]);
3740
3740
  const skip = useCallback8((toolCallId) => {
3741
- const onSkip = latest.current.options.onSkip;
3741
+ const onSkip = latest.current.handlers.onSkip;
3742
3742
  if (onSkip) {
3743
- settle(toolCallId, "skip", () => onSkip(toolCallId));
3743
+ settle(toolCallId, "skip", () => onSkip({
3744
+ toolCallId
3745
+ }));
3744
3746
  }
3745
3747
  }, [
3746
3748
  settle
3747
3749
  ]);
3748
3750
  const approve = useCallback8((toolCallId, options2) => {
3749
- const onApprove = latest.current.options.onApprove;
3751
+ const onApprove = latest.current.handlers.onApprove;
3750
3752
  if (onApprove) {
3751
- settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
3753
+ settle(toolCallId, "approve", () => onApprove({
3754
+ toolCallId,
3755
+ ...options2?.remember === true ? {
3756
+ remember: true
3757
+ } : {}
3758
+ }));
3752
3759
  }
3753
3760
  }, [
3754
3761
  settle
3755
3762
  ]);
3756
3763
  const reject = useCallback8((toolCallId) => {
3757
- const onReject = latest.current.options.onReject;
3764
+ const onReject = latest.current.handlers.onReject;
3758
3765
  if (onReject) {
3759
- settle(toolCallId, "reject", () => onReject(toolCallId));
3766
+ settle(toolCallId, "reject", () => onReject({
3767
+ toolCallId
3768
+ }));
3760
3769
  }
3761
3770
  }, [
3762
3771
  settle
@@ -3811,13 +3820,20 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3811
3820
  return;
3812
3821
  }
3813
3822
  created.cancelEdit();
3814
- void latest.current.options.onEditSubmit?.(id, next);
3823
+ void latest.current.options.onEditSubmit?.({
3824
+ messageId: id,
3825
+ text: next
3826
+ });
3815
3827
  }, "save"),
3816
3828
  fork: /* @__PURE__ */ __name(() => {
3817
- void latest.current.options.onFork?.(id);
3829
+ void latest.current.options.onFork?.({
3830
+ messageId: id
3831
+ });
3818
3832
  }, "fork"),
3819
3833
  regenerate: /* @__PURE__ */ __name(() => {
3820
- void latest.current.options.onRegenerate?.(id);
3834
+ void latest.current.options.onRegenerate?.({
3835
+ messageId: id
3836
+ });
3821
3837
  }, "regenerate"),
3822
3838
  textareaRef: /* @__PURE__ */ __name((element) => {
3823
3839
  if (!element) {
@@ -3854,7 +3870,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3854
3870
  const draft = drafts.get(message.id);
3855
3871
  const isEditing = draft !== void 0;
3856
3872
  const text = extractMessageText(message.parts);
3857
- const usage = options.getUsage?.(message) ?? null;
3873
+ const usage = (options.getUsage ?? usageFromMetadata)(message);
3858
3874
  const isUser = message.role === "user";
3859
3875
  const isAssistant = message.role === "assistant";
3860
3876
  const isLastAssistant = isAssistant && message.id === lastAssistantId;
@@ -3876,22 +3892,22 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3876
3892
  ...options.toolCatalog !== void 0 ? {
3877
3893
  toolCatalog: options.toolCatalog
3878
3894
  } : {},
3879
- ...options.onAnswer !== void 0 ? {
3895
+ ...handlers.onAnswer !== void 0 ? {
3880
3896
  elicitation: {
3881
3897
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
3882
3898
  pick,
3883
3899
  canAnswer: true,
3884
- canSkip: options.onSkip !== void 0,
3900
+ canSkip: handlers.onSkip !== void 0,
3885
3901
  answer,
3886
3902
  skip,
3887
3903
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
3888
3904
  errorOf: /* @__PURE__ */ __name((toolCallId) => settleErrors.get(toolCallId) ?? null, "errorOf")
3889
3905
  }
3890
3906
  } : {},
3891
- ...options.onApprove !== void 0 || options.onReject !== void 0 ? {
3907
+ ...handlers.onApprove !== void 0 || handlers.onReject !== void 0 ? {
3892
3908
  approval: {
3893
- canApprove: options.onApprove !== void 0,
3894
- canReject: options.onReject !== void 0,
3909
+ canApprove: handlers.onApprove !== void 0,
3910
+ canReject: handlers.onReject !== void 0,
3895
3911
  approve,
3896
3912
  reject,
3897
3913
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
@@ -3901,7 +3917,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3901
3917
  }),
3902
3918
  text,
3903
3919
  usage: usage ? describeUsage(usage) : null,
3904
- timestamp: describeTimestamp(options.getCreatedAt?.(message)),
3920
+ timestamp: describeTimestamp((options.getCreatedAt ?? createdAtFromMetadata)(message)),
3905
3921
  copy: {
3906
3922
  available: text.length > 0,
3907
3923
  copied: copied.has(message.id),
@@ -3936,6 +3952,43 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3936
3952
  return items;
3937
3953
  }
3938
3954
  __name(useTranscriptItems, "useTranscriptItems");
3955
+ function settleHandlers(options, backend) {
3956
+ const pick = /* @__PURE__ */ __name((own, fallback2) => own === null ? void 0 : own ?? fallback2, "pick");
3957
+ return {
3958
+ onAnswer: pick(options.onAnswer, backend.answerToolCall ? async (input) => {
3959
+ await backend.answerToolCall?.(input);
3960
+ } : void 0),
3961
+ onSkip: pick(options.onSkip, backend.skipToolCall ? async (input) => {
3962
+ await backend.skipToolCall?.(input);
3963
+ } : void 0),
3964
+ onApprove: pick(options.onApprove, backend.approveToolCall ? async (input) => {
3965
+ await backend.approveToolCall?.(input);
3966
+ } : void 0),
3967
+ onReject: pick(options.onReject, backend.rejectToolCall ? async (input) => {
3968
+ await backend.rejectToolCall?.(input);
3969
+ } : void 0)
3970
+ };
3971
+ }
3972
+ __name(settleHandlers, "settleHandlers");
3973
+ function metadataOf2(message) {
3974
+ const metadata = message.metadata;
3975
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
3976
+ }
3977
+ __name(metadataOf2, "metadataOf");
3978
+ function usageFromMetadata(message) {
3979
+ const usage = metadataOf2(message).usage;
3980
+ if (usage === void 0) return null;
3981
+ return {
3982
+ inputTokens: usage.inputTokens,
3983
+ outputTokens: usage.outputTokens,
3984
+ costUsd: usage.costUsd ?? null
3985
+ };
3986
+ }
3987
+ __name(usageFromMetadata, "usageFromMetadata");
3988
+ function createdAtFromMetadata(message) {
3989
+ return metadataOf2(message).createdAt ?? null;
3990
+ }
3991
+ __name(createdAtFromMetadata, "createdAtFromMetadata");
3939
3992
  function pickKey(toolCallId, questionId) {
3940
3993
  return `${toolCallId}|${questionId}`;
3941
3994
  }
@@ -3985,7 +4038,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
3985
4038
  editable
3986
4039
  } : {},
3987
4040
  ...onEditSubmit ? {
3988
- onEditSubmit: /* @__PURE__ */ __name((_id, next) => onEditSubmit(next), "onEditSubmit")
4041
+ onEditSubmit
3989
4042
  } : {},
3990
4043
  ...onFork ? {
3991
4044
  onFork
@@ -3994,7 +4047,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
3994
4047
  regeneratable
3995
4048
  } : {},
3996
4049
  ...onRegenerate ? {
3997
- onRegenerate: /* @__PURE__ */ __name(() => onRegenerate(), "onRegenerate")
4050
+ onRegenerate
3998
4051
  } : {},
3999
4052
  ...usage != null ? {
4000
4053
  getUsage: /* @__PURE__ */ __name(() => usage, "getUsage")
@@ -4699,7 +4752,7 @@ function useToolCatalog(options = {}) {
4699
4752
  setLoading(true);
4700
4753
  load(client, agent).then((loaded) => {
4701
4754
  if (cancelled) return;
4702
- setEntries(loaded);
4755
+ setEntries(Array.isArray(loaded) ? loaded : []);
4703
4756
  setError(null);
4704
4757
  setLoading(false);
4705
4758
  }, (failure) => {
@@ -4927,14 +4980,27 @@ function storedMessageToUiMessage(message) {
4927
4980
  });
4928
4981
  parts.push(...pushedByCall.get(call.id) ?? []);
4929
4982
  }
4983
+ const metadata = {
4984
+ ...message.feedback !== void 0 ? {
4985
+ feedback: message.feedback
4986
+ } : {},
4987
+ ...message.createdAt ? {
4988
+ createdAt: message.createdAt
4989
+ } : {},
4990
+ ...message.usage !== void 0 ? {
4991
+ usage: {
4992
+ inputTokens: message.usage.inputTokens,
4993
+ outputTokens: message.usage.outputTokens,
4994
+ costUsd: message.usage.costUsd ?? null
4995
+ }
4996
+ } : {}
4997
+ };
4930
4998
  return {
4931
4999
  id: message.id,
4932
5000
  role: message.role,
4933
5001
  parts,
4934
- ...message.feedback !== void 0 ? {
4935
- metadata: {
4936
- feedback: message.feedback
4937
- }
5002
+ ...Object.keys(metadata).length > 0 ? {
5003
+ metadata
4938
5004
  } : {}
4939
5005
  };
4940
5006
  }
@@ -4972,12 +5038,16 @@ function mergeTurn(rows) {
4972
5038
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
4973
5039
  const usage = sumUsage(rows);
4974
5040
  const feedback = lastRow.feedback;
5041
+ const createdAt = rows[0]?.createdAt;
4975
5042
  const metadata = {
4976
5043
  ...usage !== void 0 ? {
4977
5044
  usage
4978
5045
  } : {},
4979
5046
  ...feedback !== void 0 ? {
4980
5047
  feedback
5048
+ } : {},
5049
+ ...createdAt ? {
5050
+ createdAt
4981
5051
  } : {}
4982
5052
  };
4983
5053
  return {
@@ -5007,50 +5077,61 @@ __name(sumUsage, "sumUsage");
5007
5077
  // src/use-agent-chat.ts
5008
5078
  import { useChat } from "@ai-sdk/react";
5009
5079
  import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo11, useRef as useRef12, useState as useState14 } from "react";
5080
+ var localChatSequence = 0;
5081
+ function localChatId() {
5082
+ localChatSequence += 1;
5083
+ return `local-chat-${localChatSequence}`;
5084
+ }
5085
+ __name(localChatId, "localChatId");
5010
5086
  function useAgentChat(options = {}) {
5011
5087
  const latest = useRef12(options);
5012
5088
  latest.current = options;
5089
+ const resolved = useAgentBackend(options.backend);
5090
+ const client = useRef12(resolved).current;
5091
+ const createdThreadId = useRef12(void 0);
5092
+ const chatIdRef = useRef12(options.threadId ?? localChatId());
5093
+ const boundThreadRef = useRef12(options.threadId);
5094
+ if (options.threadId !== boundThreadRef.current) {
5095
+ boundThreadRef.current = options.threadId;
5096
+ const adopting = options.threadId !== void 0 && options.threadId === createdThreadId.current;
5097
+ if (!adopting) {
5098
+ chatIdRef.current = options.threadId ?? localChatId();
5099
+ createdThreadId.current = void 0;
5100
+ }
5101
+ }
5102
+ const chatId = chatIdRef.current;
5013
5103
  const [runId, setRunId] = useState14(options.resumeRunId);
5014
5104
  const runIdRef = useRef12(runId);
5015
5105
  runIdRef.current = runId;
5016
5106
  const settlingRunIdRef = useRef12(void 0);
5017
5107
  const autoResumeRunIdRef = useRef12(void 0);
5018
- const [autoResume, setAutoResume] = useState14(false);
5019
5108
  const [activeRunId, setActiveRunId] = useState14(null);
5109
+ const [isLoadingHistory, setIsLoadingHistory] = useState14(false);
5110
+ const [historyError, setHistoryError] = useState14(null);
5111
+ const [threadModel, setThreadModel] = useState14(null);
5112
+ const [pickedModel, setPickedModel] = useState14(void 0);
5113
+ const pickedModelRef = useRef12(pickedModel);
5114
+ pickedModelRef.current = pickedModel;
5115
+ const pendingPin = useRef12(void 0);
5020
5116
  const regenerateNext = useRef12(false);
5021
- const createdThreadId = useRef12(void 0);
5022
- const resolved = useAgentBackend(options.backend);
5023
- const client = useRef12(resolved).current;
5117
+ const currentThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
5024
5118
  const [connection, setConnection] = useState14({
5025
5119
  status: "live"
5026
5120
  });
5027
5121
  const resyncAfterSettle = useRef12(false);
5028
- useEffect14(() => {
5029
- if (!options.resume || options.threadId === void 0) return;
5030
- let cancelled = false;
5031
- const threadId = options.threadId;
5032
- void client.getThread(threadId).then((thread) => {
5033
- if (cancelled) return;
5034
- setActiveRunId(thread.activeRunId ?? null);
5035
- if (thread.activeRunId != null) {
5036
- autoResumeRunIdRef.current = thread.activeRunId;
5037
- setAutoResume(true);
5038
- }
5039
- });
5040
- return () => {
5041
- cancelled = true;
5042
- };
5043
- }, [
5044
- client,
5045
- options.resume,
5046
- options.threadId
5047
- ]);
5048
5122
  const transport = useMemo11(() => {
5049
5123
  function onMeta(meta) {
5050
5124
  settlingRunIdRef.current = meta.runId;
5051
5125
  setRunId(meta.runId);
5052
5126
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
5053
5127
  createdThreadId.current = meta.threadId;
5128
+ const pin = pendingPin.current;
5129
+ if (pin !== void 0) {
5130
+ pendingPin.current = void 0;
5131
+ void client.updateThread(meta.threadId, {
5132
+ model: pin.model
5133
+ }).catch(() => void 0);
5134
+ }
5054
5135
  latest.current.onThreadCreated?.(meta.threadId);
5055
5136
  notifyThreads(client, {
5056
5137
  type: "changed"
@@ -5077,13 +5158,14 @@ function useAgentChat(options = {}) {
5077
5158
  const pageContext = current.getPageContext?.() ?? null;
5078
5159
  const regenerate2 = regenerateNext.current;
5079
5160
  regenerateNext.current = false;
5080
- const threadId = current.threadId ?? createdThreadId.current;
5161
+ const threadId = currentThreadId();
5162
+ const model = current.model ?? pickedModelRef.current;
5081
5163
  return {
5082
5164
  ...threadId !== void 0 ? {
5083
5165
  threadId
5084
5166
  } : {},
5085
- ...current.model !== void 0 ? {
5086
- model: current.model
5167
+ ...model !== void 0 ? {
5168
+ model
5087
5169
  } : {},
5088
5170
  ...pageContext ? {
5089
5171
  pageContext
@@ -5103,10 +5185,8 @@ function useAgentChat(options = {}) {
5103
5185
  const backgroundRef = useRef12(() => Promise.resolve());
5104
5186
  const chat = useChat({
5105
5187
  transport,
5106
- resume: options.resumeRunId !== void 0 || autoResume,
5107
- ...options.threadId !== void 0 ? {
5108
- id: options.threadId
5109
- } : {},
5188
+ id: chatId,
5189
+ resume: options.resumeRunId !== void 0,
5110
5190
  ...options.initialMessages !== void 0 ? {
5111
5191
  messages: options.initialMessages
5112
5192
  } : {},
@@ -5116,7 +5196,7 @@ function useAgentChat(options = {}) {
5116
5196
  const title = part.data?.title;
5117
5197
  if (typeof title === "string") {
5118
5198
  latest.current.onTitle?.(title);
5119
- const threadId = latest.current.threadId ?? createdThreadId.current;
5199
+ const threadId = currentThreadId();
5120
5200
  if (threadId !== void 0) notifyThreads(client, {
5121
5201
  type: "title",
5122
5202
  threadId,
@@ -5152,7 +5232,7 @@ function useAgentChat(options = {}) {
5152
5232
  const chatRef = useRef12(chat);
5153
5233
  chatRef.current = chat;
5154
5234
  async function resyncFromThread() {
5155
- const threadId = latest.current.threadId ?? createdThreadId.current;
5235
+ const threadId = currentThreadId();
5156
5236
  if (threadId === void 0) return;
5157
5237
  try {
5158
5238
  const thread = await client.getThread(threadId);
@@ -5161,13 +5241,75 @@ function useAgentChat(options = {}) {
5161
5241
  }
5162
5242
  }
5163
5243
  __name(resyncFromThread, "resyncFromThread");
5244
+ const firstChatId = useRef12(chatId);
5245
+ useEffect14(() => {
5246
+ if (chatId === firstChatId.current) return;
5247
+ firstChatId.current = chatId;
5248
+ setRunId(void 0);
5249
+ setActiveRunId(null);
5250
+ setThreadModel(null);
5251
+ setBackgroundRuns([]);
5252
+ setHistoryError(null);
5253
+ autoResumeRunIdRef.current = void 0;
5254
+ pendingPin.current = void 0;
5255
+ }, [
5256
+ chatId
5257
+ ]);
5258
+ useEffect14(() => {
5259
+ const threadId = options.threadId;
5260
+ if (threadId === void 0) return;
5261
+ if (threadId === createdThreadId.current) return;
5262
+ const current = latest.current;
5263
+ const wantHistory = current.history !== false && current.initialMessages === void 0;
5264
+ const wantResume = current.resume !== false && current.resumeRunId === void 0;
5265
+ if (!wantHistory && !wantResume) return;
5266
+ let cancelled = false;
5267
+ if (wantHistory) setIsLoadingHistory(true);
5268
+ client.getThread(threadId).then((thread) => {
5269
+ if (cancelled || thread == null) return;
5270
+ const active = thread.activeRunId ?? null;
5271
+ const stored = Array.isArray(thread.messages) ? thread.messages : [];
5272
+ setActiveRunId(active);
5273
+ setThreadModel(thread.model ?? null);
5274
+ setHistoryError(null);
5275
+ const resuming = wantResume && active !== null;
5276
+ if (wantHistory && chatRef.current.messages.length === 0) {
5277
+ const rows = resuming ? stored.filter((message) => !(message.role === "assistant" && message.runId === active)) : stored;
5278
+ chatRef.current.setMessages(storedThreadToUiMessages(rows));
5279
+ }
5280
+ if (resuming) {
5281
+ autoResumeRunIdRef.current = active;
5282
+ void chatRef.current.resumeStream();
5283
+ }
5284
+ }, (error) => {
5285
+ if (cancelled) return;
5286
+ setHistoryError(error instanceof Error ? error : new Error(String(error)));
5287
+ }).finally(() => {
5288
+ if (!cancelled) setIsLoadingHistory(false);
5289
+ });
5290
+ return () => {
5291
+ cancelled = true;
5292
+ setIsLoadingHistory(false);
5293
+ };
5294
+ }, [
5295
+ client,
5296
+ chatId,
5297
+ options.threadId
5298
+ ]);
5164
5299
  const turnInFlight = useRef12(false);
5165
5300
  const isTurnInFlight = useCallback11(() => turnInFlight.current || transport.isAttemptLive, [
5166
5301
  transport
5167
5302
  ]);
5303
+ const quota = useQuota({
5304
+ backend: client,
5305
+ enabled: options.quota !== false && typeof client.getQuota === "function"
5306
+ });
5307
+ const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
5308
+ const blockedRef = useRef12(blocked);
5309
+ blockedRef.current = blocked;
5168
5310
  const sendMessage = useCallback11(async (...args) => {
5169
- const blocked = latest.current.blocked;
5170
- if (blocked != null) throw new QuotaBlockedError(blocked);
5311
+ const block = blockedRef.current;
5312
+ if (block != null) throw new QuotaBlockedError(block);
5171
5313
  if (isTurnInFlight()) return;
5172
5314
  turnInFlight.current = true;
5173
5315
  try {
@@ -5183,11 +5325,9 @@ function useAgentChat(options = {}) {
5183
5325
  toolCallId,
5184
5326
  output
5185
5327
  }), []);
5186
- const [threads, setThreads] = useState14([]);
5187
- const [quota, setQuota] = useState14(null);
5188
5328
  const [backgroundRuns, setBackgroundRuns] = useState14([]);
5189
5329
  const refreshBackground = useCallback11(async () => {
5190
- const threadId = latest.current.threadId ?? createdThreadId.current;
5330
+ const threadId = currentThreadId();
5191
5331
  if (threadId === void 0) {
5192
5332
  return;
5193
5333
  }
@@ -5207,7 +5347,8 @@ function useAgentChat(options = {}) {
5207
5347
  ];
5208
5348
  });
5209
5349
  }, [
5210
- client
5350
+ client,
5351
+ currentThreadId
5211
5352
  ]);
5212
5353
  backgroundRef.current = refreshBackground;
5213
5354
  const isWorking = backgroundRuns.some((run) => run.status === "running");
@@ -5219,6 +5360,7 @@ function useAgentChat(options = {}) {
5219
5360
  }, [
5220
5361
  options.background,
5221
5362
  options.threadId,
5363
+ chatId,
5222
5364
  refreshBackground
5223
5365
  ]);
5224
5366
  useEffect14(() => {
@@ -5232,59 +5374,12 @@ function useAgentChat(options = {}) {
5232
5374
  isWorking,
5233
5375
  refreshBackground
5234
5376
  ]);
5235
- const loadThreads = useCallback11(async () => {
5236
- const list = await client.listThreads();
5237
- setThreads(list);
5238
- return list;
5239
- }, [
5240
- client
5241
- ]);
5242
- const loadThread = useCallback11((id) => client.getThread(id), [
5243
- client
5244
- ]);
5245
- const deleteThread = useCallback11(async (id) => {
5246
- await client.deleteThread(id);
5247
- setThreads((current) => current.filter((thread) => thread.id !== id));
5248
- }, [
5249
- client
5250
- ]);
5251
- const forkThread = useCallback11((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
5252
- client
5253
- ]);
5254
- const renameThread = useCallback11(async (id, title) => {
5255
- await client.updateThread(id, {
5256
- title
5257
- });
5258
- setThreads((current) => current.map((thread) => thread.id === id ? {
5259
- ...thread,
5260
- title
5261
- } : thread));
5262
- }, [
5263
- client
5264
- ]);
5265
- const promoteThread = useCallback11(async (id) => {
5266
- await requireBackendMethod(client, "promoteThread")(id);
5267
- await loadThreads();
5268
- }, [
5269
- client,
5270
- loadThreads
5271
- ]);
5272
- const truncateFromMessage = useCallback11((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
5273
- client
5274
- ]);
5275
- const loadQuota = useCallback11(async () => {
5276
- const today = await requireBackendMethod(client, "getQuotaToday")();
5277
- setQuota(today);
5278
- return today;
5279
- }, [
5280
- client
5281
- ]);
5282
5377
  const cancel = useCallback11(async () => {
5283
5378
  chatRef.current.stop();
5284
- const activeRunId2 = runIdRef.current;
5285
- if (activeRunId2) {
5379
+ const active = runIdRef.current;
5380
+ if (active) {
5286
5381
  try {
5287
- await client.cancelStream(activeRunId2);
5382
+ await client.cancelStream(active);
5288
5383
  } catch {
5289
5384
  }
5290
5385
  }
@@ -5334,9 +5429,9 @@ function useAgentChat(options = {}) {
5334
5429
  }, [
5335
5430
  client
5336
5431
  ]);
5337
- const regenerate = useCallback11(() => {
5338
- const blocked = latest.current.blocked;
5339
- if (blocked != null) throw new QuotaBlockedError(blocked);
5432
+ const regenerate = useCallback11((_input) => {
5433
+ const block = blockedRef.current;
5434
+ if (block != null) throw new QuotaBlockedError(block);
5340
5435
  if (isTurnInFlight()) return;
5341
5436
  regenerateNext.current = true;
5342
5437
  turnInFlight.current = true;
@@ -5346,15 +5441,78 @@ function useAgentChat(options = {}) {
5346
5441
  }, [
5347
5442
  isTurnInFlight
5348
5443
  ]);
5349
- const background = {
5350
- runs: backgroundRuns,
5351
- isWorking,
5352
- refresh: refreshBackground
5353
- };
5354
- const setThreadModel = useCallback11(async (model) => {
5355
- const threadId = latest.current.threadId ?? createdThreadId.current;
5444
+ const storedIdOf = useCallback11(async (messageId, threadId) => {
5445
+ const message = chatRef.current.messages.find((candidate) => candidate.id === messageId);
5446
+ const runOf = message?.metadata?.runId;
5447
+ if (runOf === void 0) return messageId;
5448
+ const thread = await client.getThread(threadId);
5449
+ const row = [
5450
+ ...thread.messages
5451
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runOf);
5452
+ return row?.id ?? messageId;
5453
+ }, [
5454
+ client
5455
+ ]);
5456
+ const fork = useCallback11(async ({ messageId, threadId }) => {
5457
+ const source = threadId ?? currentThreadId();
5458
+ if (source === void 0) throw new Error("fork: this chat has no thread yet");
5459
+ const stored = await storedIdOf(messageId, source);
5460
+ const forked = await requireBackendMethod(client, "forkFromMessage")(source, stored);
5461
+ notifyThreads(client, {
5462
+ type: "changed"
5463
+ });
5464
+ latest.current.onThreadCreated?.(forked.id);
5465
+ return forked;
5466
+ }, [
5467
+ client,
5468
+ currentThreadId,
5469
+ storedIdOf
5470
+ ]);
5471
+ const truncateFrom = useCallback11(async ({ messageId, threadId }) => {
5472
+ const target = threadId ?? currentThreadId();
5473
+ if (target === void 0) throw new Error("truncateFrom: this chat has no thread yet");
5474
+ const stored = await storedIdOf(messageId, target);
5475
+ await requireBackendMethod(client, "truncateFromMessage")(target, stored);
5476
+ }, [
5477
+ client,
5478
+ currentThreadId,
5479
+ storedIdOf
5480
+ ]);
5481
+ const promote = useCallback11(async ({ threadId } = {}) => {
5482
+ const target = threadId ?? currentThreadId();
5483
+ if (target === void 0) throw new Error("promote: this chat has no thread yet");
5484
+ await requireBackendMethod(client, "promoteThread")(target);
5485
+ notifyThreads(client, {
5486
+ type: "changed"
5487
+ });
5488
+ }, [
5489
+ client,
5490
+ currentThreadId
5491
+ ]);
5492
+ const [modelsWanted, setModelsWanted] = useState14(false);
5493
+ const modelsWantedRef = useRef12(modelsWanted);
5494
+ modelsWantedRef.current = modelsWanted;
5495
+ const catalog = useModels({
5496
+ backend: client,
5497
+ ...options.agent !== void 0 ? {
5498
+ agent: options.agent
5499
+ } : {},
5500
+ enabled: modelsWanted && typeof client.listModels === "function"
5501
+ });
5502
+ const wantModels = useCallback11(() => {
5503
+ if (modelsWantedRef.current) return;
5504
+ modelsWantedRef.current = true;
5505
+ queueMicrotask(() => setModelsWanted(true));
5506
+ }, []);
5507
+ const selectModel = useCallback11((id) => setPickedModel(id), []);
5508
+ const pinToThread = useCallback11(async (model) => {
5509
+ const threadId = currentThreadId();
5510
+ setThreadModel(model);
5356
5511
  if (threadId === void 0) {
5357
- throw new Error("setThreadModel: this chat has no thread yet");
5512
+ pendingPin.current = {
5513
+ model
5514
+ };
5515
+ return;
5358
5516
  }
5359
5517
  await client.updateThread(threadId, {
5360
5518
  model
@@ -5363,10 +5521,99 @@ function useAgentChat(options = {}) {
5363
5521
  type: "changed"
5364
5522
  });
5365
5523
  }, [
5366
- client
5524
+ client,
5525
+ currentThreadId
5367
5526
  ]);
5368
- const getThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
5527
+ const models = {
5528
+ get list() {
5529
+ wantModels();
5530
+ return catalog.models;
5531
+ },
5532
+ get providers() {
5533
+ wantModels();
5534
+ return catalog.providers;
5535
+ },
5536
+ selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5537
+ select: selectModel,
5538
+ pinToThread,
5539
+ isLoading: modelsWanted && catalog.isLoading,
5540
+ error: catalog.error
5541
+ };
5369
5542
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
5543
+ const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
5544
+ const files = useAttachments({
5545
+ ...options.composer,
5546
+ backend: client
5547
+ });
5548
+ const [text, setText] = useState14("");
5549
+ const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy ? "busy" : text.trim().length === 0 && files.attachments.length === 0 ? "empty" : null;
5550
+ const composerRef = useRef12({
5551
+ text,
5552
+ files,
5553
+ blockedBy
5554
+ });
5555
+ composerRef.current = {
5556
+ text,
5557
+ files,
5558
+ blockedBy
5559
+ };
5560
+ const submit = useCallback11(async () => {
5561
+ const current = composerRef.current;
5562
+ if (current.blockedBy !== null) return;
5563
+ const refs = current.files.refs;
5564
+ const draft = current.text.trim();
5565
+ setText("");
5566
+ current.files.clear();
5567
+ await sendMessage({
5568
+ text: draft
5569
+ }, refs.length > 0 ? {
5570
+ body: {
5571
+ attachments: refs
5572
+ }
5573
+ } : void 0);
5574
+ }, [
5575
+ sendMessage
5576
+ ]);
5577
+ const composer = {
5578
+ text,
5579
+ setText,
5580
+ files,
5581
+ canSend: blockedBy === null,
5582
+ blockedBy,
5583
+ submit
5584
+ };
5585
+ const ownCatalog = options.transcript?.toolCatalog;
5586
+ const tools = useToolCatalog({
5587
+ backend: client,
5588
+ ...options.agent !== void 0 ? {
5589
+ agent: options.agent
5590
+ } : {},
5591
+ enabled: ownCatalog === void 0 && typeof client.listTools === "function"
5592
+ });
5593
+ const canFork = typeof client.forkFromMessage === "function";
5594
+ const transcript = useChatTranscript({
5595
+ messages: chat.messages,
5596
+ status,
5597
+ onStop: cancel,
5598
+ onApprove: approve,
5599
+ onReject: reject,
5600
+ onAnswer: answer,
5601
+ onSkip: skip,
5602
+ regeneratable: true,
5603
+ onRegenerate: regenerate,
5604
+ ...canFork ? {
5605
+ onFork: /* @__PURE__ */ __name((input) => fork(input).then(() => void 0), "onFork")
5606
+ } : {},
5607
+ ...ownCatalog === void 0 ? {
5608
+ toolCatalog: tools.catalog
5609
+ } : {},
5610
+ ...options.transcript
5611
+ });
5612
+ const background = {
5613
+ runs: backgroundRuns,
5614
+ isWorking,
5615
+ refresh: refreshBackground
5616
+ };
5370
5617
  return {
5371
5618
  ...chat,
5372
5619
  status,
@@ -5377,32 +5624,38 @@ function useAgentChat(options = {}) {
5377
5624
  /** Sub-agents this conversation started and did not wait for. */
5378
5625
  background,
5379
5626
  runId,
5380
- /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
5627
+ /** The loaded thread's running turn, or `null` once read with none. */
5381
5628
  activeRunId,
5629
+ /** The thread's history is being read. */
5630
+ isLoadingHistory,
5631
+ /** Reading the thread's history failed. */
5632
+ historyError,
5382
5633
  /**
5383
5634
  * The thread this chat is on right now: the `threadId` option, else the one the backend created
5384
5635
  * on the first send. A getter, because the created id is learned mid-stream.
5385
5636
  */
5386
- getThreadId,
5387
- setThreadModel,
5637
+ getThreadId: currentThreadId,
5388
5638
  /** The backend this chat talks to — the same one every other hook under the provider uses. */
5389
5639
  backend: client,
5390
- threads,
5391
- loadThreads,
5392
- loadThread,
5393
- deleteThread,
5394
- forkThread,
5395
- renameThread,
5396
- promoteThread,
5397
- truncateFromMessage,
5640
+ /** The transcript model, with every action already bound to this chat. */
5641
+ transcript,
5642
+ /** A headless composer: draft, files, send gate, `submit()`. */
5643
+ composer,
5644
+ /** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
5645
+ models,
5646
+ /** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
5398
5647
  quota,
5399
- loadQuota,
5648
+ /** The window blocking sends right now (the `blocked` option, else the quota report's). */
5649
+ blocked,
5400
5650
  cancel,
5401
5651
  approve,
5402
5652
  reject,
5403
5653
  answer,
5404
5654
  skip,
5405
- regenerate
5655
+ regenerate,
5656
+ fork,
5657
+ truncateFrom,
5658
+ promote
5406
5659
  };
5407
5660
  }
5408
5661
  __name(useAgentChat, "useAgentChat");