@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.cjs CHANGED
@@ -217,11 +217,6 @@ var AgentClient = class {
217
217
  forkFromMessage(threadId, messageId) {
218
218
  return this.request("POST", `/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
219
219
  }
220
- renameThread(id, title) {
221
- return this.updateThread(id, {
222
- title
223
- });
224
- }
225
220
  /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
226
221
  updateThread(id, patch) {
227
222
  return this.request("PATCH", `/threads/${encodeURIComponent(id)}`, patch);
@@ -314,9 +309,6 @@ var AgentClient = class {
314
309
  getQuota() {
315
310
  return this.request("GET", "/quota");
316
311
  }
317
- getQuotaToday() {
318
- return this.request("GET", "/quota/today");
319
- }
320
312
  cancelStream(runId) {
321
313
  return this.request("POST", `/chat/${encodeURIComponent(runId)}/cancel`);
322
314
  }
@@ -728,13 +720,14 @@ var AgentChatTransport = class {
728
720
  function ensureStarted() {
729
721
  if (started) return;
730
722
  started = true;
731
- controller.enqueue(streamRunId !== void 0 ? {
723
+ controller.enqueue({
732
724
  type: "start",
733
725
  messageMetadata: {
734
- runId: streamRunId
726
+ ...streamRunId !== void 0 ? {
727
+ runId: streamRunId
728
+ } : {},
729
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
735
730
  }
736
- } : {
737
- type: "start"
738
731
  });
739
732
  }
740
733
  __name(ensureStarted, "ensureStarted");
@@ -3764,14 +3757,18 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3764
3757
  const [picks, setPicks] = (0, import_react16.useState)(() => /* @__PURE__ */ new Map());
3765
3758
  const [settling, setSettling] = (0, import_react16.useState)(() => /* @__PURE__ */ new Map());
3766
3759
  const [settleErrors, setSettleErrors] = (0, import_react16.useState)(() => /* @__PURE__ */ new Map());
3760
+ const backend = useAgentBackend();
3761
+ const handlers = settleHandlers(options, backend);
3767
3762
  const latest = (0, import_react16.useRef)({
3768
3763
  options,
3764
+ handlers,
3769
3765
  drafts,
3770
3766
  picks,
3771
3767
  messages
3772
3768
  });
3773
3769
  latest.current = {
3774
3770
  options,
3771
+ handlers,
3775
3772
  drafts,
3776
3773
  picks,
3777
3774
  messages
@@ -3831,34 +3828,46 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3831
3828
  }, []);
3832
3829
  const answer = (0, import_react16.useCallback)((toolCallId) => {
3833
3830
  const current = latest.current;
3834
- const onAnswer = current.options.onAnswer;
3831
+ const onAnswer = current.handlers.onAnswer;
3835
3832
  if (!onAnswer) {
3836
3833
  return;
3837
3834
  }
3838
- settle(toolCallId, "answer", () => onAnswer(toolCallId, answersFor(current.picks, toolCallId)));
3835
+ settle(toolCallId, "answer", () => onAnswer({
3836
+ toolCallId,
3837
+ answers: answersFor(current.picks, toolCallId)
3838
+ }));
3839
3839
  }, [
3840
3840
  settle
3841
3841
  ]);
3842
3842
  const skip = (0, import_react16.useCallback)((toolCallId) => {
3843
- const onSkip = latest.current.options.onSkip;
3843
+ const onSkip = latest.current.handlers.onSkip;
3844
3844
  if (onSkip) {
3845
- settle(toolCallId, "skip", () => onSkip(toolCallId));
3845
+ settle(toolCallId, "skip", () => onSkip({
3846
+ toolCallId
3847
+ }));
3846
3848
  }
3847
3849
  }, [
3848
3850
  settle
3849
3851
  ]);
3850
3852
  const approve = (0, import_react16.useCallback)((toolCallId, options2) => {
3851
- const onApprove = latest.current.options.onApprove;
3853
+ const onApprove = latest.current.handlers.onApprove;
3852
3854
  if (onApprove) {
3853
- settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
3855
+ settle(toolCallId, "approve", () => onApprove({
3856
+ toolCallId,
3857
+ ...options2?.remember === true ? {
3858
+ remember: true
3859
+ } : {}
3860
+ }));
3854
3861
  }
3855
3862
  }, [
3856
3863
  settle
3857
3864
  ]);
3858
3865
  const reject = (0, import_react16.useCallback)((toolCallId) => {
3859
- const onReject = latest.current.options.onReject;
3866
+ const onReject = latest.current.handlers.onReject;
3860
3867
  if (onReject) {
3861
- settle(toolCallId, "reject", () => onReject(toolCallId));
3868
+ settle(toolCallId, "reject", () => onReject({
3869
+ toolCallId
3870
+ }));
3862
3871
  }
3863
3872
  }, [
3864
3873
  settle
@@ -3913,13 +3922,20 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3913
3922
  return;
3914
3923
  }
3915
3924
  created.cancelEdit();
3916
- void latest.current.options.onEditSubmit?.(id, next);
3925
+ void latest.current.options.onEditSubmit?.({
3926
+ messageId: id,
3927
+ text: next
3928
+ });
3917
3929
  }, "save"),
3918
3930
  fork: /* @__PURE__ */ __name(() => {
3919
- void latest.current.options.onFork?.(id);
3931
+ void latest.current.options.onFork?.({
3932
+ messageId: id
3933
+ });
3920
3934
  }, "fork"),
3921
3935
  regenerate: /* @__PURE__ */ __name(() => {
3922
- void latest.current.options.onRegenerate?.(id);
3936
+ void latest.current.options.onRegenerate?.({
3937
+ messageId: id
3938
+ });
3923
3939
  }, "regenerate"),
3924
3940
  textareaRef: /* @__PURE__ */ __name((element) => {
3925
3941
  if (!element) {
@@ -3956,7 +3972,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3956
3972
  const draft = drafts.get(message.id);
3957
3973
  const isEditing = draft !== void 0;
3958
3974
  const text = extractMessageText(message.parts);
3959
- const usage = options.getUsage?.(message) ?? null;
3975
+ const usage = (options.getUsage ?? usageFromMetadata)(message);
3960
3976
  const isUser = message.role === "user";
3961
3977
  const isAssistant = message.role === "assistant";
3962
3978
  const isLastAssistant = isAssistant && message.id === lastAssistantId;
@@ -3978,22 +3994,22 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3978
3994
  ...options.toolCatalog !== void 0 ? {
3979
3995
  toolCatalog: options.toolCatalog
3980
3996
  } : {},
3981
- ...options.onAnswer !== void 0 ? {
3997
+ ...handlers.onAnswer !== void 0 ? {
3982
3998
  elicitation: {
3983
3999
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
3984
4000
  pick,
3985
4001
  canAnswer: true,
3986
- canSkip: options.onSkip !== void 0,
4002
+ canSkip: handlers.onSkip !== void 0,
3987
4003
  answer,
3988
4004
  skip,
3989
4005
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
3990
4006
  errorOf: /* @__PURE__ */ __name((toolCallId) => settleErrors.get(toolCallId) ?? null, "errorOf")
3991
4007
  }
3992
4008
  } : {},
3993
- ...options.onApprove !== void 0 || options.onReject !== void 0 ? {
4009
+ ...handlers.onApprove !== void 0 || handlers.onReject !== void 0 ? {
3994
4010
  approval: {
3995
- canApprove: options.onApprove !== void 0,
3996
- canReject: options.onReject !== void 0,
4011
+ canApprove: handlers.onApprove !== void 0,
4012
+ canReject: handlers.onReject !== void 0,
3997
4013
  approve,
3998
4014
  reject,
3999
4015
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
@@ -4003,7 +4019,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4003
4019
  }),
4004
4020
  text,
4005
4021
  usage: usage ? describeUsage(usage) : null,
4006
- timestamp: describeTimestamp(options.getCreatedAt?.(message)),
4022
+ timestamp: describeTimestamp((options.getCreatedAt ?? createdAtFromMetadata)(message)),
4007
4023
  copy: {
4008
4024
  available: text.length > 0,
4009
4025
  copied: copied.has(message.id),
@@ -4038,6 +4054,43 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4038
4054
  return items;
4039
4055
  }
4040
4056
  __name(useTranscriptItems, "useTranscriptItems");
4057
+ function settleHandlers(options, backend) {
4058
+ const pick = /* @__PURE__ */ __name((own, fallback2) => own === null ? void 0 : own ?? fallback2, "pick");
4059
+ return {
4060
+ onAnswer: pick(options.onAnswer, backend.answerToolCall ? async (input) => {
4061
+ await backend.answerToolCall?.(input);
4062
+ } : void 0),
4063
+ onSkip: pick(options.onSkip, backend.skipToolCall ? async (input) => {
4064
+ await backend.skipToolCall?.(input);
4065
+ } : void 0),
4066
+ onApprove: pick(options.onApprove, backend.approveToolCall ? async (input) => {
4067
+ await backend.approveToolCall?.(input);
4068
+ } : void 0),
4069
+ onReject: pick(options.onReject, backend.rejectToolCall ? async (input) => {
4070
+ await backend.rejectToolCall?.(input);
4071
+ } : void 0)
4072
+ };
4073
+ }
4074
+ __name(settleHandlers, "settleHandlers");
4075
+ function metadataOf2(message) {
4076
+ const metadata = message.metadata;
4077
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
4078
+ }
4079
+ __name(metadataOf2, "metadataOf");
4080
+ function usageFromMetadata(message) {
4081
+ const usage = metadataOf2(message).usage;
4082
+ if (usage === void 0) return null;
4083
+ return {
4084
+ inputTokens: usage.inputTokens,
4085
+ outputTokens: usage.outputTokens,
4086
+ costUsd: usage.costUsd ?? null
4087
+ };
4088
+ }
4089
+ __name(usageFromMetadata, "usageFromMetadata");
4090
+ function createdAtFromMetadata(message) {
4091
+ return metadataOf2(message).createdAt ?? null;
4092
+ }
4093
+ __name(createdAtFromMetadata, "createdAtFromMetadata");
4041
4094
  function pickKey(toolCallId, questionId) {
4042
4095
  return `${toolCallId}|${questionId}`;
4043
4096
  }
@@ -4087,7 +4140,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
4087
4140
  editable
4088
4141
  } : {},
4089
4142
  ...onEditSubmit ? {
4090
- onEditSubmit: /* @__PURE__ */ __name((_id, next) => onEditSubmit(next), "onEditSubmit")
4143
+ onEditSubmit
4091
4144
  } : {},
4092
4145
  ...onFork ? {
4093
4146
  onFork
@@ -4096,7 +4149,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
4096
4149
  regeneratable
4097
4150
  } : {},
4098
4151
  ...onRegenerate ? {
4099
- onRegenerate: /* @__PURE__ */ __name(() => onRegenerate(), "onRegenerate")
4152
+ onRegenerate
4100
4153
  } : {},
4101
4154
  ...usage != null ? {
4102
4155
  getUsage: /* @__PURE__ */ __name(() => usage, "getUsage")
@@ -4801,7 +4854,7 @@ function useToolCatalog(options = {}) {
4801
4854
  setLoading(true);
4802
4855
  load(client, agent).then((loaded) => {
4803
4856
  if (cancelled) return;
4804
- setEntries(loaded);
4857
+ setEntries(Array.isArray(loaded) ? loaded : []);
4805
4858
  setError(null);
4806
4859
  setLoading(false);
4807
4860
  }, (failure) => {
@@ -5029,14 +5082,27 @@ function storedMessageToUiMessage(message) {
5029
5082
  });
5030
5083
  parts.push(...pushedByCall.get(call.id) ?? []);
5031
5084
  }
5085
+ const metadata = {
5086
+ ...message.feedback !== void 0 ? {
5087
+ feedback: message.feedback
5088
+ } : {},
5089
+ ...message.createdAt ? {
5090
+ createdAt: message.createdAt
5091
+ } : {},
5092
+ ...message.usage !== void 0 ? {
5093
+ usage: {
5094
+ inputTokens: message.usage.inputTokens,
5095
+ outputTokens: message.usage.outputTokens,
5096
+ costUsd: message.usage.costUsd ?? null
5097
+ }
5098
+ } : {}
5099
+ };
5032
5100
  return {
5033
5101
  id: message.id,
5034
5102
  role: message.role,
5035
5103
  parts,
5036
- ...message.feedback !== void 0 ? {
5037
- metadata: {
5038
- feedback: message.feedback
5039
- }
5104
+ ...Object.keys(metadata).length > 0 ? {
5105
+ metadata
5040
5106
  } : {}
5041
5107
  };
5042
5108
  }
@@ -5074,12 +5140,16 @@ function mergeTurn(rows) {
5074
5140
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
5075
5141
  const usage = sumUsage(rows);
5076
5142
  const feedback = lastRow.feedback;
5143
+ const createdAt = rows[0]?.createdAt;
5077
5144
  const metadata = {
5078
5145
  ...usage !== void 0 ? {
5079
5146
  usage
5080
5147
  } : {},
5081
5148
  ...feedback !== void 0 ? {
5082
5149
  feedback
5150
+ } : {},
5151
+ ...createdAt ? {
5152
+ createdAt
5083
5153
  } : {}
5084
5154
  };
5085
5155
  return {
@@ -5109,50 +5179,61 @@ __name(sumUsage, "sumUsage");
5109
5179
  // src/use-agent-chat.ts
5110
5180
  var import_react21 = require("@ai-sdk/react");
5111
5181
  var import_react22 = require("react");
5182
+ var localChatSequence = 0;
5183
+ function localChatId() {
5184
+ localChatSequence += 1;
5185
+ return `local-chat-${localChatSequence}`;
5186
+ }
5187
+ __name(localChatId, "localChatId");
5112
5188
  function useAgentChat(options = {}) {
5113
5189
  const latest = (0, import_react22.useRef)(options);
5114
5190
  latest.current = options;
5191
+ const resolved = useAgentBackend(options.backend);
5192
+ const client = (0, import_react22.useRef)(resolved).current;
5193
+ const createdThreadId = (0, import_react22.useRef)(void 0);
5194
+ const chatIdRef = (0, import_react22.useRef)(options.threadId ?? localChatId());
5195
+ const boundThreadRef = (0, import_react22.useRef)(options.threadId);
5196
+ if (options.threadId !== boundThreadRef.current) {
5197
+ boundThreadRef.current = options.threadId;
5198
+ const adopting = options.threadId !== void 0 && options.threadId === createdThreadId.current;
5199
+ if (!adopting) {
5200
+ chatIdRef.current = options.threadId ?? localChatId();
5201
+ createdThreadId.current = void 0;
5202
+ }
5203
+ }
5204
+ const chatId = chatIdRef.current;
5115
5205
  const [runId, setRunId] = (0, import_react22.useState)(options.resumeRunId);
5116
5206
  const runIdRef = (0, import_react22.useRef)(runId);
5117
5207
  runIdRef.current = runId;
5118
5208
  const settlingRunIdRef = (0, import_react22.useRef)(void 0);
5119
5209
  const autoResumeRunIdRef = (0, import_react22.useRef)(void 0);
5120
- const [autoResume, setAutoResume] = (0, import_react22.useState)(false);
5121
5210
  const [activeRunId, setActiveRunId] = (0, import_react22.useState)(null);
5211
+ const [isLoadingHistory, setIsLoadingHistory] = (0, import_react22.useState)(false);
5212
+ const [historyError, setHistoryError] = (0, import_react22.useState)(null);
5213
+ const [threadModel, setThreadModel] = (0, import_react22.useState)(null);
5214
+ const [pickedModel, setPickedModel] = (0, import_react22.useState)(void 0);
5215
+ const pickedModelRef = (0, import_react22.useRef)(pickedModel);
5216
+ pickedModelRef.current = pickedModel;
5217
+ const pendingPin = (0, import_react22.useRef)(void 0);
5122
5218
  const regenerateNext = (0, import_react22.useRef)(false);
5123
- const createdThreadId = (0, import_react22.useRef)(void 0);
5124
- const resolved = useAgentBackend(options.backend);
5125
- const client = (0, import_react22.useRef)(resolved).current;
5219
+ const currentThreadId = (0, import_react22.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
5126
5220
  const [connection, setConnection] = (0, import_react22.useState)({
5127
5221
  status: "live"
5128
5222
  });
5129
5223
  const resyncAfterSettle = (0, import_react22.useRef)(false);
5130
- (0, import_react22.useEffect)(() => {
5131
- if (!options.resume || options.threadId === void 0) return;
5132
- let cancelled = false;
5133
- const threadId = options.threadId;
5134
- void client.getThread(threadId).then((thread) => {
5135
- if (cancelled) return;
5136
- setActiveRunId(thread.activeRunId ?? null);
5137
- if (thread.activeRunId != null) {
5138
- autoResumeRunIdRef.current = thread.activeRunId;
5139
- setAutoResume(true);
5140
- }
5141
- });
5142
- return () => {
5143
- cancelled = true;
5144
- };
5145
- }, [
5146
- client,
5147
- options.resume,
5148
- options.threadId
5149
- ]);
5150
5224
  const transport = (0, import_react22.useMemo)(() => {
5151
5225
  function onMeta(meta) {
5152
5226
  settlingRunIdRef.current = meta.runId;
5153
5227
  setRunId(meta.runId);
5154
5228
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
5155
5229
  createdThreadId.current = meta.threadId;
5230
+ const pin = pendingPin.current;
5231
+ if (pin !== void 0) {
5232
+ pendingPin.current = void 0;
5233
+ void client.updateThread(meta.threadId, {
5234
+ model: pin.model
5235
+ }).catch(() => void 0);
5236
+ }
5156
5237
  latest.current.onThreadCreated?.(meta.threadId);
5157
5238
  notifyThreads(client, {
5158
5239
  type: "changed"
@@ -5179,13 +5260,14 @@ function useAgentChat(options = {}) {
5179
5260
  const pageContext = current.getPageContext?.() ?? null;
5180
5261
  const regenerate2 = regenerateNext.current;
5181
5262
  regenerateNext.current = false;
5182
- const threadId = current.threadId ?? createdThreadId.current;
5263
+ const threadId = currentThreadId();
5264
+ const model = current.model ?? pickedModelRef.current;
5183
5265
  return {
5184
5266
  ...threadId !== void 0 ? {
5185
5267
  threadId
5186
5268
  } : {},
5187
- ...current.model !== void 0 ? {
5188
- model: current.model
5269
+ ...model !== void 0 ? {
5270
+ model
5189
5271
  } : {},
5190
5272
  ...pageContext ? {
5191
5273
  pageContext
@@ -5205,10 +5287,8 @@ function useAgentChat(options = {}) {
5205
5287
  const backgroundRef = (0, import_react22.useRef)(() => Promise.resolve());
5206
5288
  const chat = (0, import_react21.useChat)({
5207
5289
  transport,
5208
- resume: options.resumeRunId !== void 0 || autoResume,
5209
- ...options.threadId !== void 0 ? {
5210
- id: options.threadId
5211
- } : {},
5290
+ id: chatId,
5291
+ resume: options.resumeRunId !== void 0,
5212
5292
  ...options.initialMessages !== void 0 ? {
5213
5293
  messages: options.initialMessages
5214
5294
  } : {},
@@ -5218,7 +5298,7 @@ function useAgentChat(options = {}) {
5218
5298
  const title = part.data?.title;
5219
5299
  if (typeof title === "string") {
5220
5300
  latest.current.onTitle?.(title);
5221
- const threadId = latest.current.threadId ?? createdThreadId.current;
5301
+ const threadId = currentThreadId();
5222
5302
  if (threadId !== void 0) notifyThreads(client, {
5223
5303
  type: "title",
5224
5304
  threadId,
@@ -5254,7 +5334,7 @@ function useAgentChat(options = {}) {
5254
5334
  const chatRef = (0, import_react22.useRef)(chat);
5255
5335
  chatRef.current = chat;
5256
5336
  async function resyncFromThread() {
5257
- const threadId = latest.current.threadId ?? createdThreadId.current;
5337
+ const threadId = currentThreadId();
5258
5338
  if (threadId === void 0) return;
5259
5339
  try {
5260
5340
  const thread = await client.getThread(threadId);
@@ -5263,13 +5343,75 @@ function useAgentChat(options = {}) {
5263
5343
  }
5264
5344
  }
5265
5345
  __name(resyncFromThread, "resyncFromThread");
5346
+ const firstChatId = (0, import_react22.useRef)(chatId);
5347
+ (0, import_react22.useEffect)(() => {
5348
+ if (chatId === firstChatId.current) return;
5349
+ firstChatId.current = chatId;
5350
+ setRunId(void 0);
5351
+ setActiveRunId(null);
5352
+ setThreadModel(null);
5353
+ setBackgroundRuns([]);
5354
+ setHistoryError(null);
5355
+ autoResumeRunIdRef.current = void 0;
5356
+ pendingPin.current = void 0;
5357
+ }, [
5358
+ chatId
5359
+ ]);
5360
+ (0, import_react22.useEffect)(() => {
5361
+ const threadId = options.threadId;
5362
+ if (threadId === void 0) return;
5363
+ if (threadId === createdThreadId.current) return;
5364
+ const current = latest.current;
5365
+ const wantHistory = current.history !== false && current.initialMessages === void 0;
5366
+ const wantResume = current.resume !== false && current.resumeRunId === void 0;
5367
+ if (!wantHistory && !wantResume) return;
5368
+ let cancelled = false;
5369
+ if (wantHistory) setIsLoadingHistory(true);
5370
+ client.getThread(threadId).then((thread) => {
5371
+ if (cancelled || thread == null) return;
5372
+ const active = thread.activeRunId ?? null;
5373
+ const stored = Array.isArray(thread.messages) ? thread.messages : [];
5374
+ setActiveRunId(active);
5375
+ setThreadModel(thread.model ?? null);
5376
+ setHistoryError(null);
5377
+ const resuming = wantResume && active !== null;
5378
+ if (wantHistory && chatRef.current.messages.length === 0) {
5379
+ const rows = resuming ? stored.filter((message) => !(message.role === "assistant" && message.runId === active)) : stored;
5380
+ chatRef.current.setMessages(storedThreadToUiMessages(rows));
5381
+ }
5382
+ if (resuming) {
5383
+ autoResumeRunIdRef.current = active;
5384
+ void chatRef.current.resumeStream();
5385
+ }
5386
+ }, (error) => {
5387
+ if (cancelled) return;
5388
+ setHistoryError(error instanceof Error ? error : new Error(String(error)));
5389
+ }).finally(() => {
5390
+ if (!cancelled) setIsLoadingHistory(false);
5391
+ });
5392
+ return () => {
5393
+ cancelled = true;
5394
+ setIsLoadingHistory(false);
5395
+ };
5396
+ }, [
5397
+ client,
5398
+ chatId,
5399
+ options.threadId
5400
+ ]);
5266
5401
  const turnInFlight = (0, import_react22.useRef)(false);
5267
5402
  const isTurnInFlight = (0, import_react22.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
5268
5403
  transport
5269
5404
  ]);
5405
+ const quota = useQuota({
5406
+ backend: client,
5407
+ enabled: options.quota !== false && typeof client.getQuota === "function"
5408
+ });
5409
+ const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
5410
+ const blockedRef = (0, import_react22.useRef)(blocked);
5411
+ blockedRef.current = blocked;
5270
5412
  const sendMessage = (0, import_react22.useCallback)(async (...args) => {
5271
- const blocked = latest.current.blocked;
5272
- if (blocked != null) throw new QuotaBlockedError(blocked);
5413
+ const block = blockedRef.current;
5414
+ if (block != null) throw new QuotaBlockedError(block);
5273
5415
  if (isTurnInFlight()) return;
5274
5416
  turnInFlight.current = true;
5275
5417
  try {
@@ -5285,11 +5427,9 @@ function useAgentChat(options = {}) {
5285
5427
  toolCallId,
5286
5428
  output
5287
5429
  }), []);
5288
- const [threads, setThreads] = (0, import_react22.useState)([]);
5289
- const [quota, setQuota] = (0, import_react22.useState)(null);
5290
5430
  const [backgroundRuns, setBackgroundRuns] = (0, import_react22.useState)([]);
5291
5431
  const refreshBackground = (0, import_react22.useCallback)(async () => {
5292
- const threadId = latest.current.threadId ?? createdThreadId.current;
5432
+ const threadId = currentThreadId();
5293
5433
  if (threadId === void 0) {
5294
5434
  return;
5295
5435
  }
@@ -5309,7 +5449,8 @@ function useAgentChat(options = {}) {
5309
5449
  ];
5310
5450
  });
5311
5451
  }, [
5312
- client
5452
+ client,
5453
+ currentThreadId
5313
5454
  ]);
5314
5455
  backgroundRef.current = refreshBackground;
5315
5456
  const isWorking = backgroundRuns.some((run) => run.status === "running");
@@ -5321,6 +5462,7 @@ function useAgentChat(options = {}) {
5321
5462
  }, [
5322
5463
  options.background,
5323
5464
  options.threadId,
5465
+ chatId,
5324
5466
  refreshBackground
5325
5467
  ]);
5326
5468
  (0, import_react22.useEffect)(() => {
@@ -5334,59 +5476,12 @@ function useAgentChat(options = {}) {
5334
5476
  isWorking,
5335
5477
  refreshBackground
5336
5478
  ]);
5337
- const loadThreads = (0, import_react22.useCallback)(async () => {
5338
- const list = await client.listThreads();
5339
- setThreads(list);
5340
- return list;
5341
- }, [
5342
- client
5343
- ]);
5344
- const loadThread = (0, import_react22.useCallback)((id) => client.getThread(id), [
5345
- client
5346
- ]);
5347
- const deleteThread = (0, import_react22.useCallback)(async (id) => {
5348
- await client.deleteThread(id);
5349
- setThreads((current) => current.filter((thread) => thread.id !== id));
5350
- }, [
5351
- client
5352
- ]);
5353
- const forkThread = (0, import_react22.useCallback)((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
5354
- client
5355
- ]);
5356
- const renameThread = (0, import_react22.useCallback)(async (id, title) => {
5357
- await client.updateThread(id, {
5358
- title
5359
- });
5360
- setThreads((current) => current.map((thread) => thread.id === id ? {
5361
- ...thread,
5362
- title
5363
- } : thread));
5364
- }, [
5365
- client
5366
- ]);
5367
- const promoteThread = (0, import_react22.useCallback)(async (id) => {
5368
- await requireBackendMethod(client, "promoteThread")(id);
5369
- await loadThreads();
5370
- }, [
5371
- client,
5372
- loadThreads
5373
- ]);
5374
- const truncateFromMessage = (0, import_react22.useCallback)((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
5375
- client
5376
- ]);
5377
- const loadQuota = (0, import_react22.useCallback)(async () => {
5378
- const today = await requireBackendMethod(client, "getQuotaToday")();
5379
- setQuota(today);
5380
- return today;
5381
- }, [
5382
- client
5383
- ]);
5384
5479
  const cancel = (0, import_react22.useCallback)(async () => {
5385
5480
  chatRef.current.stop();
5386
- const activeRunId2 = runIdRef.current;
5387
- if (activeRunId2) {
5481
+ const active = runIdRef.current;
5482
+ if (active) {
5388
5483
  try {
5389
- await client.cancelStream(activeRunId2);
5484
+ await client.cancelStream(active);
5390
5485
  } catch {
5391
5486
  }
5392
5487
  }
@@ -5436,9 +5531,9 @@ function useAgentChat(options = {}) {
5436
5531
  }, [
5437
5532
  client
5438
5533
  ]);
5439
- const regenerate = (0, import_react22.useCallback)(() => {
5440
- const blocked = latest.current.blocked;
5441
- if (blocked != null) throw new QuotaBlockedError(blocked);
5534
+ const regenerate = (0, import_react22.useCallback)((_input) => {
5535
+ const block = blockedRef.current;
5536
+ if (block != null) throw new QuotaBlockedError(block);
5442
5537
  if (isTurnInFlight()) return;
5443
5538
  regenerateNext.current = true;
5444
5539
  turnInFlight.current = true;
@@ -5448,15 +5543,78 @@ function useAgentChat(options = {}) {
5448
5543
  }, [
5449
5544
  isTurnInFlight
5450
5545
  ]);
5451
- const background = {
5452
- runs: backgroundRuns,
5453
- isWorking,
5454
- refresh: refreshBackground
5455
- };
5456
- const setThreadModel = (0, import_react22.useCallback)(async (model) => {
5457
- const threadId = latest.current.threadId ?? createdThreadId.current;
5546
+ const storedIdOf = (0, import_react22.useCallback)(async (messageId, threadId) => {
5547
+ const message = chatRef.current.messages.find((candidate) => candidate.id === messageId);
5548
+ const runOf = message?.metadata?.runId;
5549
+ if (runOf === void 0) return messageId;
5550
+ const thread = await client.getThread(threadId);
5551
+ const row = [
5552
+ ...thread.messages
5553
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runOf);
5554
+ return row?.id ?? messageId;
5555
+ }, [
5556
+ client
5557
+ ]);
5558
+ const fork = (0, import_react22.useCallback)(async ({ messageId, threadId }) => {
5559
+ const source = threadId ?? currentThreadId();
5560
+ if (source === void 0) throw new Error("fork: this chat has no thread yet");
5561
+ const stored = await storedIdOf(messageId, source);
5562
+ const forked = await requireBackendMethod(client, "forkFromMessage")(source, stored);
5563
+ notifyThreads(client, {
5564
+ type: "changed"
5565
+ });
5566
+ latest.current.onThreadCreated?.(forked.id);
5567
+ return forked;
5568
+ }, [
5569
+ client,
5570
+ currentThreadId,
5571
+ storedIdOf
5572
+ ]);
5573
+ const truncateFrom = (0, import_react22.useCallback)(async ({ messageId, threadId }) => {
5574
+ const target = threadId ?? currentThreadId();
5575
+ if (target === void 0) throw new Error("truncateFrom: this chat has no thread yet");
5576
+ const stored = await storedIdOf(messageId, target);
5577
+ await requireBackendMethod(client, "truncateFromMessage")(target, stored);
5578
+ }, [
5579
+ client,
5580
+ currentThreadId,
5581
+ storedIdOf
5582
+ ]);
5583
+ const promote = (0, import_react22.useCallback)(async ({ threadId } = {}) => {
5584
+ const target = threadId ?? currentThreadId();
5585
+ if (target === void 0) throw new Error("promote: this chat has no thread yet");
5586
+ await requireBackendMethod(client, "promoteThread")(target);
5587
+ notifyThreads(client, {
5588
+ type: "changed"
5589
+ });
5590
+ }, [
5591
+ client,
5592
+ currentThreadId
5593
+ ]);
5594
+ const [modelsWanted, setModelsWanted] = (0, import_react22.useState)(false);
5595
+ const modelsWantedRef = (0, import_react22.useRef)(modelsWanted);
5596
+ modelsWantedRef.current = modelsWanted;
5597
+ const catalog = useModels({
5598
+ backend: client,
5599
+ ...options.agent !== void 0 ? {
5600
+ agent: options.agent
5601
+ } : {},
5602
+ enabled: modelsWanted && typeof client.listModels === "function"
5603
+ });
5604
+ const wantModels = (0, import_react22.useCallback)(() => {
5605
+ if (modelsWantedRef.current) return;
5606
+ modelsWantedRef.current = true;
5607
+ queueMicrotask(() => setModelsWanted(true));
5608
+ }, []);
5609
+ const selectModel = (0, import_react22.useCallback)((id) => setPickedModel(id), []);
5610
+ const pinToThread = (0, import_react22.useCallback)(async (model) => {
5611
+ const threadId = currentThreadId();
5612
+ setThreadModel(model);
5458
5613
  if (threadId === void 0) {
5459
- throw new Error("setThreadModel: this chat has no thread yet");
5614
+ pendingPin.current = {
5615
+ model
5616
+ };
5617
+ return;
5460
5618
  }
5461
5619
  await client.updateThread(threadId, {
5462
5620
  model
@@ -5465,10 +5623,99 @@ function useAgentChat(options = {}) {
5465
5623
  type: "changed"
5466
5624
  });
5467
5625
  }, [
5468
- client
5626
+ client,
5627
+ currentThreadId
5469
5628
  ]);
5470
- const getThreadId = (0, import_react22.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
5629
+ const models = {
5630
+ get list() {
5631
+ wantModels();
5632
+ return catalog.models;
5633
+ },
5634
+ get providers() {
5635
+ wantModels();
5636
+ return catalog.providers;
5637
+ },
5638
+ selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5639
+ select: selectModel,
5640
+ pinToThread,
5641
+ isLoading: modelsWanted && catalog.isLoading,
5642
+ error: catalog.error
5643
+ };
5471
5644
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
5645
+ const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
5646
+ const files = useAttachments({
5647
+ ...options.composer,
5648
+ backend: client
5649
+ });
5650
+ const [text, setText] = (0, import_react22.useState)("");
5651
+ const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy ? "busy" : text.trim().length === 0 && files.attachments.length === 0 ? "empty" : null;
5652
+ const composerRef = (0, import_react22.useRef)({
5653
+ text,
5654
+ files,
5655
+ blockedBy
5656
+ });
5657
+ composerRef.current = {
5658
+ text,
5659
+ files,
5660
+ blockedBy
5661
+ };
5662
+ const submit = (0, import_react22.useCallback)(async () => {
5663
+ const current = composerRef.current;
5664
+ if (current.blockedBy !== null) return;
5665
+ const refs = current.files.refs;
5666
+ const draft = current.text.trim();
5667
+ setText("");
5668
+ current.files.clear();
5669
+ await sendMessage({
5670
+ text: draft
5671
+ }, refs.length > 0 ? {
5672
+ body: {
5673
+ attachments: refs
5674
+ }
5675
+ } : void 0);
5676
+ }, [
5677
+ sendMessage
5678
+ ]);
5679
+ const composer = {
5680
+ text,
5681
+ setText,
5682
+ files,
5683
+ canSend: blockedBy === null,
5684
+ blockedBy,
5685
+ submit
5686
+ };
5687
+ const ownCatalog = options.transcript?.toolCatalog;
5688
+ const tools = useToolCatalog({
5689
+ backend: client,
5690
+ ...options.agent !== void 0 ? {
5691
+ agent: options.agent
5692
+ } : {},
5693
+ enabled: ownCatalog === void 0 && typeof client.listTools === "function"
5694
+ });
5695
+ const canFork = typeof client.forkFromMessage === "function";
5696
+ const transcript = useChatTranscript({
5697
+ messages: chat.messages,
5698
+ status,
5699
+ onStop: cancel,
5700
+ onApprove: approve,
5701
+ onReject: reject,
5702
+ onAnswer: answer,
5703
+ onSkip: skip,
5704
+ regeneratable: true,
5705
+ onRegenerate: regenerate,
5706
+ ...canFork ? {
5707
+ onFork: /* @__PURE__ */ __name((input) => fork(input).then(() => void 0), "onFork")
5708
+ } : {},
5709
+ ...ownCatalog === void 0 ? {
5710
+ toolCatalog: tools.catalog
5711
+ } : {},
5712
+ ...options.transcript
5713
+ });
5714
+ const background = {
5715
+ runs: backgroundRuns,
5716
+ isWorking,
5717
+ refresh: refreshBackground
5718
+ };
5472
5719
  return {
5473
5720
  ...chat,
5474
5721
  status,
@@ -5479,32 +5726,38 @@ function useAgentChat(options = {}) {
5479
5726
  /** Sub-agents this conversation started and did not wait for. */
5480
5727
  background,
5481
5728
  runId,
5482
- /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
5729
+ /** The loaded thread's running turn, or `null` once read with none. */
5483
5730
  activeRunId,
5731
+ /** The thread's history is being read. */
5732
+ isLoadingHistory,
5733
+ /** Reading the thread's history failed. */
5734
+ historyError,
5484
5735
  /**
5485
5736
  * The thread this chat is on right now: the `threadId` option, else the one the backend created
5486
5737
  * on the first send. A getter, because the created id is learned mid-stream.
5487
5738
  */
5488
- getThreadId,
5489
- setThreadModel,
5739
+ getThreadId: currentThreadId,
5490
5740
  /** The backend this chat talks to — the same one every other hook under the provider uses. */
5491
5741
  backend: client,
5492
- threads,
5493
- loadThreads,
5494
- loadThread,
5495
- deleteThread,
5496
- forkThread,
5497
- renameThread,
5498
- promoteThread,
5499
- truncateFromMessage,
5742
+ /** The transcript model, with every action already bound to this chat. */
5743
+ transcript,
5744
+ /** A headless composer: draft, files, send gate, `submit()`. */
5745
+ composer,
5746
+ /** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
5747
+ models,
5748
+ /** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
5500
5749
  quota,
5501
- loadQuota,
5750
+ /** The window blocking sends right now (the `blocked` option, else the quota report's). */
5751
+ blocked,
5502
5752
  cancel,
5503
5753
  approve,
5504
5754
  reject,
5505
5755
  answer,
5506
5756
  skip,
5507
- regenerate
5757
+ regenerate,
5758
+ fork,
5759
+ truncateFrom,
5760
+ promote
5508
5761
  };
5509
5762
  }
5510
5763
  __name(useAgentChat, "useAgentChat");