@dudousxd/nestjs-agent-react 0.24.0 → 0.26.0

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
@@ -1,17 +1,82 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/http-error.ts
5
+ function readErrorAnswer(text) {
6
+ if (text == null || text === "") return {
7
+ body: void 0,
8
+ message: void 0,
9
+ code: void 0
10
+ };
11
+ let body = text;
12
+ try {
13
+ body = JSON.parse(text);
14
+ } catch {
15
+ return {
16
+ body: text,
17
+ message: void 0,
18
+ code: void 0
19
+ };
20
+ }
21
+ if (body === null || typeof body !== "object") return {
22
+ body,
23
+ message: void 0,
24
+ code: void 0
25
+ };
26
+ const record = body;
27
+ const message = typeof record.message === "string" && record.message !== "" ? record.message : Array.isArray(record.message) && record.message.length > 0 && record.message.every((each) => typeof each === "string") ? record.message.join("; ") : void 0;
28
+ const code = typeof record.code === "string" && record.code !== "" ? record.code : void 0;
29
+ return {
30
+ body,
31
+ message,
32
+ code
33
+ };
34
+ }
35
+ __name(readErrorAnswer, "readErrorAnswer");
36
+ async function readErrorResponse(response) {
37
+ let text;
38
+ try {
39
+ text = await response.text();
40
+ } catch {
41
+ text = void 0;
42
+ }
43
+ return readErrorAnswer(text);
44
+ }
45
+ __name(readErrorResponse, "readErrorResponse");
46
+ function reportHttpError(listener, error) {
47
+ if (listener === void 0) return;
48
+ try {
49
+ listener(error);
50
+ } catch {
51
+ }
52
+ }
53
+ __name(reportHttpError, "reportHttpError");
54
+
4
55
  // src/client.ts
5
- var AgentHttpError = class extends Error {
56
+ var AgentHttpError = class _AgentHttpError extends Error {
6
57
  static {
7
58
  __name(this, "AgentHttpError");
8
59
  }
9
60
  status;
10
61
  method;
11
62
  path;
12
- constructor(status, method, path, statusText) {
13
- super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
63
+ /** The answer's body: parsed JSON, else its text; `undefined` when empty. */
64
+ body;
65
+ /** The body's `code` (`quota_exceeded`, …), when the server sent one. */
66
+ code;
67
+ constructor(status, method, path, statusText, answer = {
68
+ body: void 0,
69
+ message: void 0,
70
+ code: void 0
71
+ }) {
72
+ super(answer.message ?? `Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
14
73
  this.name = "AgentHttpError";
74
+ this.body = answer.body;
75
+ this.code = answer.code;
76
+ }
77
+ /** Read a refused `response`'s body into an error. */
78
+ static async from(response, method, path) {
79
+ return new _AgentHttpError(response.status, method, path, response.statusText, await readErrorResponse(response));
15
80
  }
16
81
  };
17
82
  function normalizeAgentPath(path) {
@@ -46,7 +111,7 @@ var AgentClient = class {
46
111
  } : {}
47
112
  });
48
113
  if (!response.ok || !response.body) {
49
- throw new AgentHttpError(response.status, "POST", `${this.agentPath()}/chat`, response.statusText);
114
+ throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
50
115
  }
51
116
  return streamResponse(response);
52
117
  }
@@ -73,7 +138,7 @@ var AgentClient = class {
73
138
  return null;
74
139
  }
75
140
  if (!response.ok || !response.body) {
76
- throw new AgentHttpError(response.status, "GET", `${this.agentPath()}${path}`, response.statusText);
141
+ throw await this.failure(response, "GET", `${this.agentPath()}${path}`);
77
142
  }
78
143
  return streamResponse(response);
79
144
  }
@@ -166,7 +231,9 @@ var AgentClient = class {
166
231
  };
167
232
  xhr.onload = () => {
168
233
  if (xhr.status < 200 || xhr.status >= 300) {
169
- reject(new AgentHttpError(xhr.status, "POST", `${this.agentPath()}/attachments`, xhr.statusText));
234
+ const error = new AgentHttpError(xhr.status, "POST", `${this.agentPath()}/attachments`, xhr.statusText, readErrorAnswer(xhr.responseText));
235
+ reportHttpError(this.options.onHttpError, error);
236
+ reject(error);
170
237
  return;
171
238
  }
172
239
  onProgress?.(1);
@@ -229,6 +296,7 @@ var AgentClient = class {
229
296
  * out takes the pre-picked default the request carried, resolved server-side against the request
230
297
  * the run already holds. Omit the whole object and the user has confirmed every pre-picked
231
298
  * answer — which is the point of the surface, so it is a valid submission rather than a blank.
299
+ * `via` names the surface the answer came through (the server records `'web'` when omitted).
232
300
  */
233
301
  answerToolCall(input) {
234
302
  return this.request("POST", "/tool-call/answer", input);
@@ -251,7 +319,10 @@ var AgentClient = class {
251
319
  path: this.agentPath(),
252
320
  headers: /* @__PURE__ */ __name(() => this.resolveHeaders(), "headers"),
253
321
  fetch: this.fetchImpl(),
254
- ...this.credentials()
322
+ ...this.credentials(),
323
+ ...this.options.onHttpError !== void 0 ? {
324
+ onHttpError: this.options.onHttpError
325
+ } : {}
255
326
  };
256
327
  }
257
328
  baseUrl() {
@@ -294,9 +365,15 @@ var AgentClient = class {
294
365
  });
295
366
  return this.handleResponse(response, method, path);
296
367
  }
368
+ /** The error for a refused `response`, already reported to `onHttpError`. */
369
+ async failure(response, method, path) {
370
+ const error = await AgentHttpError.from(response, method, path);
371
+ reportHttpError(this.options.onHttpError, error);
372
+ return error;
373
+ }
297
374
  async handleResponse(response, method, path) {
298
375
  if (!response.ok) {
299
- throw new AgentHttpError(response.status, method, path, response.statusText);
376
+ throw await this.failure(response, method, path);
300
377
  }
301
378
  if (response.status === 204) return void 0;
302
379
  const text = await response.text();
@@ -1651,14 +1728,16 @@ __name(GenerativeUI, "GenerativeUI");
1651
1728
 
1652
1729
  // src/provider.tsx
1653
1730
  var AgentBackendContext = sharedContext("@dudousxd/nestjs-agent-react:agent-backend");
1654
- function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credentials, fetch, attachments, genui, children }) {
1731
+ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credentials, fetch, onHttpError, attachments, genui, children }) {
1655
1732
  const latestHeaders = useRef2({
1656
1733
  headers,
1657
- getHeaders
1734
+ getHeaders,
1735
+ onHttpError
1658
1736
  });
1659
1737
  latestHeaders.current = {
1660
1738
  headers,
1661
- getHeaders
1739
+ getHeaders,
1740
+ onHttpError
1662
1741
  };
1663
1742
  const upload = attachments?.upload;
1664
1743
  const value = useMemo3(() => backend ?? new AgentClient({
@@ -1679,6 +1758,7 @@ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credential
1679
1758
  upload
1680
1759
  }
1681
1760
  } : {},
1761
+ onHttpError: /* @__PURE__ */ __name((error) => latestHeaders.current.onHttpError?.(error), "onHttpError"),
1682
1762
  getHeaders: /* @__PURE__ */ __name(async () => {
1683
1763
  const current = latestHeaders.current;
1684
1764
  const dynamic = await current.getHeaders?.() ?? {};
@@ -2092,6 +2172,7 @@ function useModels(options = {}) {
2092
2172
  providers,
2093
2173
  models,
2094
2174
  defaultModel: data?.default ?? null,
2175
+ locked: data?.locked ?? null,
2095
2176
  find: /* @__PURE__ */ __name((id) => id == null ? void 0 : byId.get(id), "find"),
2096
2177
  isLoading,
2097
2178
  error,
@@ -2107,6 +2188,7 @@ function useModels(options = {}) {
2107
2188
  __name(useModels, "useModels");
2108
2189
 
2109
2190
  // src/quota/use-quota.ts
2191
+ import { quotaWarning } from "@dudousxd/nestjs-agent-core";
2110
2192
  import { useEffect as useEffect6, useMemo as useMemo7 } from "react";
2111
2193
 
2112
2194
  // src/threads/threads-events.ts
@@ -2162,6 +2244,7 @@ function useQuota(options = {}) {
2162
2244
  day: windows.find((window2) => window2.period === "day"),
2163
2245
  month: windows.find((window2) => window2.period === "month"),
2164
2246
  blocked: data?.blocked ?? null,
2247
+ warning: data === void 0 || data.blocked !== void 0 ? null : data.warning ?? quotaWarning(windows) ?? null,
2165
2248
  isLoading,
2166
2249
  error,
2167
2250
  refresh
@@ -3250,7 +3333,9 @@ function readElicitationOutcome(part) {
3250
3333
  answers,
3251
3334
  skipped: output.skipped,
3252
3335
  defaulted: Array.isArray(output.defaulted) ? output.defaulted.filter((id) => typeof id === "string") : [],
3253
- summary: typeof output.summary === "string" ? output.summary : null
3336
+ summary: typeof output.summary === "string" ? output.summary : null,
3337
+ answeredBy: typeof output.answeredBy === "string" ? output.answeredBy : null,
3338
+ answeredVia: typeof output.answeredVia === "string" ? output.answeredVia : null
3254
3339
  };
3255
3340
  }
3256
3341
  __name(readElicitationOutcome, "readElicitationOutcome");
@@ -3700,7 +3785,7 @@ function useChatTranscript(options) {
3700
3785
  },
3701
3786
  scroll,
3702
3787
  stop: {
3703
- available: isBusy && options.onStop !== void 0,
3788
+ available: isBusy && options.readOnly !== true && options.onStop !== void 0,
3704
3789
  isStopping: stopRequested,
3705
3790
  stop
3706
3791
  }
@@ -3970,11 +4055,12 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3970
4055
  ...options.toolCatalog !== void 0 ? {
3971
4056
  toolCatalog: options.toolCatalog
3972
4057
  } : {},
3973
- ...handlers.onAnswer !== void 0 ? {
4058
+ // Read-only still lifts question sets into their block, so the outcome shows.
4059
+ ...handlers.onAnswer !== void 0 || options.readOnly === true && options.onAnswer !== null ? {
3974
4060
  elicitation: {
3975
4061
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
3976
4062
  pick,
3977
- canAnswer: true,
4063
+ canAnswer: handlers.onAnswer !== void 0,
3978
4064
  canSkip: handlers.onSkip !== void 0,
3979
4065
  answer,
3980
4066
  skip,
@@ -4002,7 +4088,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4002
4088
  copy: callbacks.copy
4003
4089
  },
4004
4090
  edit: {
4005
- available: isUser && options.editable === true && options.onEditSubmit !== void 0,
4091
+ available: isUser && options.readOnly !== true && options.editable === true && options.onEditSubmit !== void 0,
4006
4092
  isEditing,
4007
4093
  draft: draft ?? text,
4008
4094
  canSave: (draft ?? "").trim().length > 0,
@@ -4018,11 +4104,11 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4018
4104
  }), "getTextareaProps")
4019
4105
  },
4020
4106
  fork: {
4021
- available: options.onFork !== void 0,
4107
+ available: options.readOnly !== true && options.onFork !== void 0,
4022
4108
  run: callbacks.fork
4023
4109
  },
4024
4110
  regenerate: {
4025
- available: isLastAssistant && options.regeneratable === true && options.onRegenerate !== void 0,
4111
+ available: isLastAssistant && options.readOnly !== true && options.regeneratable === true && options.onRegenerate !== void 0,
4026
4112
  run: callbacks.regenerate
4027
4113
  }
4028
4114
  });
@@ -4031,7 +4117,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
4031
4117
  }
4032
4118
  __name(useTranscriptItems, "useTranscriptItems");
4033
4119
  function settleHandlers(options, backend) {
4034
- const pick = /* @__PURE__ */ __name((own, fallback2) => own === null ? void 0 : own ?? fallback2, "pick");
4120
+ const pick = /* @__PURE__ */ __name((own, fallback2) => options.readOnly === true || own === null ? void 0 : own ?? fallback2, "pick");
4035
4121
  return {
4036
4122
  onAnswer: pick(options.onAnswer, backend.answerToolCall ? async (input) => {
4037
4123
  await backend.answerToolCall?.(input);
@@ -4324,10 +4410,13 @@ function joinClasses(...entries) {
4324
4410
  __name(joinClasses, "joinClasses");
4325
4411
 
4326
4412
  // src/components/message-list.tsx
4327
- function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
4413
+ function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, readOnly, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
4328
4414
  const transcript = useChatTranscript({
4329
4415
  messages,
4330
4416
  status,
4417
+ ...readOnly !== void 0 ? {
4418
+ readOnly
4419
+ } : {},
4331
4420
  ...onFork ? {
4332
4421
  onFork
4333
4422
  } : {},
@@ -4792,6 +4881,7 @@ function isPromise(value) {
4792
4881
  __name(isPromise, "isPromise");
4793
4882
 
4794
4883
  // src/presentation/use-tool-catalog.ts
4884
+ import { ALL_AGENTS } from "@dudousxd/nestjs-agent-core";
4795
4885
  import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo10, useState as useState13 } from "react";
4796
4886
  var shared2 = /* @__PURE__ */ new WeakMap();
4797
4887
  function load2(client, agent) {
@@ -5194,12 +5284,13 @@ function useAgentChat(options = {}) {
5194
5284
  const settlingRunIdRef = useRef12(void 0);
5195
5285
  const autoResumeRunIdRef = useRef12(void 0);
5196
5286
  const [activeRunId, setActiveRunId] = useState15(null);
5197
- const [isLoadingHistory, setIsLoadingHistory] = useState15(false);
5287
+ const [historyFor, setHistoryFor] = useState15(void 0);
5198
5288
  const [historyError, setHistoryError] = useState15(null);
5199
5289
  const [threadModel, setThreadModel] = useState15(null);
5200
5290
  const [pickedModel, setPickedModel] = useState15(void 0);
5201
5291
  const pickedModelRef = useRef12(pickedModel);
5202
5292
  pickedModelRef.current = pickedModel;
5293
+ const lockedRef = useRef12(false);
5203
5294
  const pendingPin = useRef12(void 0);
5204
5295
  const regenerateNext = useRef12(false);
5205
5296
  const currentThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
@@ -5247,7 +5338,7 @@ function useAgentChat(options = {}) {
5247
5338
  const regenerate2 = regenerateNext.current;
5248
5339
  regenerateNext.current = false;
5249
5340
  const threadId = currentThreadId();
5250
- const model = current.model ?? pickedModelRef.current;
5341
+ const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
5251
5342
  return {
5252
5343
  // Read per send: a host that switches agents before the first message (a picker on a
5253
5344
  // new chat) sends the one picked now, not the one this chat mounted with.
@@ -5341,6 +5432,7 @@ function useAgentChat(options = {}) {
5341
5432
  setRunId(void 0);
5342
5433
  setActiveRunId(null);
5343
5434
  setThreadModel(null);
5435
+ setPickedModel(void 0);
5344
5436
  setBackgroundRuns([]);
5345
5437
  setHistoryError(null);
5346
5438
  autoResumeRunIdRef.current = void 0;
@@ -5357,7 +5449,6 @@ function useAgentChat(options = {}) {
5357
5449
  const wantResume = current.resume !== false && current.resumeRunId === void 0;
5358
5450
  if (!wantHistory && !wantResume) return;
5359
5451
  let cancelled = false;
5360
- if (wantHistory) setIsLoadingHistory(true);
5361
5452
  client.getThread(threadId).then((thread) => {
5362
5453
  if (cancelled || thread == null) return;
5363
5454
  const active = thread.activeRunId ?? null;
@@ -5378,17 +5469,17 @@ function useAgentChat(options = {}) {
5378
5469
  if (cancelled) return;
5379
5470
  setHistoryError(error instanceof Error ? error : new Error(String(error)));
5380
5471
  }).finally(() => {
5381
- if (!cancelled) setIsLoadingHistory(false);
5472
+ if (!cancelled) setHistoryFor(threadId);
5382
5473
  });
5383
5474
  return () => {
5384
5475
  cancelled = true;
5385
- setIsLoadingHistory(false);
5386
5476
  };
5387
5477
  }, [
5388
5478
  client,
5389
5479
  chatId,
5390
5480
  options.threadId
5391
5481
  ]);
5482
+ const isLoadingHistory = options.threadId !== void 0 && options.threadId !== createdThreadId.current && options.history !== false && options.initialMessages === void 0 && historyFor !== options.threadId;
5392
5483
  const turnInFlight = useRef12(false);
5393
5484
  const isTurnInFlight = useCallback11(() => turnInFlight.current || transport.isAttemptLive, [
5394
5485
  transport
@@ -5505,19 +5596,25 @@ function useAgentChat(options = {}) {
5505
5596
  }, [
5506
5597
  client
5507
5598
  ]);
5508
- const answer = useCallback11(async ({ toolCallId, answers }) => {
5599
+ const answer = useCallback11(async ({ toolCallId, answers, via }) => {
5509
5600
  await requireBackendMethod(client, "answerToolCall")({
5510
5601
  toolCallId,
5511
5602
  ...answers !== void 0 ? {
5512
5603
  answers
5604
+ } : {},
5605
+ ...via !== void 0 ? {
5606
+ via
5513
5607
  } : {}
5514
5608
  });
5515
5609
  }, [
5516
5610
  client
5517
5611
  ]);
5518
- const skip = useCallback11(async ({ toolCallId }) => {
5612
+ const skip = useCallback11(async ({ toolCallId, via }) => {
5519
5613
  await requireBackendMethod(client, "skipToolCall")({
5520
- toolCallId
5614
+ toolCallId,
5615
+ ...via !== void 0 ? {
5616
+ via
5617
+ } : {}
5521
5618
  });
5522
5619
  }, [
5523
5620
  client
@@ -5597,10 +5694,14 @@ function useAgentChat(options = {}) {
5597
5694
  modelsWantedRef.current = true;
5598
5695
  queueMicrotask(() => setModelsWanted(true));
5599
5696
  }, []);
5600
- const selectModel = useCallback11((id) => setPickedModel(id), []);
5697
+ lockedRef.current = catalog.locked !== null;
5698
+ const selectModel = useCallback11((id) => {
5699
+ if (!lockedRef.current) setPickedModel(id);
5700
+ }, []);
5601
5701
  const pinToThread = useCallback11(async (model) => {
5602
5702
  const threadId = currentThreadId();
5603
5703
  setThreadModel(model);
5704
+ setPickedModel(void 0);
5604
5705
  if (threadId === void 0) {
5605
5706
  pendingPin.current = {
5606
5707
  model
@@ -5626,11 +5727,16 @@ function useAgentChat(options = {}) {
5626
5727
  wantModels();
5627
5728
  return catalog.providers;
5628
5729
  },
5629
- selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5730
+ selected: catalog.locked?.model ?? options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5731
+ pinned: threadModel,
5630
5732
  get defaultModel() {
5631
5733
  wantModels();
5632
5734
  return catalog.defaultModel;
5633
5735
  },
5736
+ get locked() {
5737
+ wantModels();
5738
+ return catalog.locked;
5739
+ },
5634
5740
  select: selectModel,
5635
5741
  pinToThread,
5636
5742
  isLoading: modelsWanted && catalog.isLoading,
@@ -5783,6 +5889,7 @@ var QuotaBlockedError = class extends Error {
5783
5889
  }
5784
5890
  };
5785
5891
  export {
5892
+ ALL_AGENTS,
5786
5893
  AgentBackendUnsupportedError,
5787
5894
  AgentChatTransport,
5788
5895
  AgentClient,