@dudousxd/nestjs-agent-react 0.23.0 → 0.25.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();
@@ -864,6 +941,13 @@ var AgentChatTransport = class {
864
941
  }
865
942
  });
866
943
  break;
944
+ case "message-metadata":
945
+ ensureStarted();
946
+ controller.enqueue({
947
+ type: "message-metadata",
948
+ messageMetadata: event.metadata
949
+ });
950
+ break;
867
951
  case "title":
868
952
  case "cancelled":
869
953
  forwardAsData(event, true);
@@ -1644,14 +1728,16 @@ __name(GenerativeUI, "GenerativeUI");
1644
1728
 
1645
1729
  // src/provider.tsx
1646
1730
  var AgentBackendContext = sharedContext("@dudousxd/nestjs-agent-react:agent-backend");
1647
- 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 }) {
1648
1732
  const latestHeaders = useRef2({
1649
1733
  headers,
1650
- getHeaders
1734
+ getHeaders,
1735
+ onHttpError
1651
1736
  });
1652
1737
  latestHeaders.current = {
1653
1738
  headers,
1654
- getHeaders
1739
+ getHeaders,
1740
+ onHttpError
1655
1741
  };
1656
1742
  const upload = attachments?.upload;
1657
1743
  const value = useMemo3(() => backend ?? new AgentClient({
@@ -1672,6 +1758,7 @@ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credential
1672
1758
  upload
1673
1759
  }
1674
1760
  } : {},
1761
+ onHttpError: /* @__PURE__ */ __name((error) => latestHeaders.current.onHttpError?.(error), "onHttpError"),
1675
1762
  getHeaders: /* @__PURE__ */ __name(async () => {
1676
1763
  const current = latestHeaders.current;
1677
1764
  const dynamic = await current.getHeaders?.() ?? {};
@@ -2085,6 +2172,7 @@ function useModels(options = {}) {
2085
2172
  providers,
2086
2173
  models,
2087
2174
  defaultModel: data?.default ?? null,
2175
+ locked: data?.locked ?? null,
2088
2176
  find: /* @__PURE__ */ __name((id) => id == null ? void 0 : byId.get(id), "find"),
2089
2177
  isLoading,
2090
2178
  error,
@@ -2100,6 +2188,7 @@ function useModels(options = {}) {
2100
2188
  __name(useModels, "useModels");
2101
2189
 
2102
2190
  // src/quota/use-quota.ts
2191
+ import { quotaWarning } from "@dudousxd/nestjs-agent-core";
2103
2192
  import { useEffect as useEffect6, useMemo as useMemo7 } from "react";
2104
2193
 
2105
2194
  // src/threads/threads-events.ts
@@ -2155,6 +2244,7 @@ function useQuota(options = {}) {
2155
2244
  day: windows.find((window2) => window2.period === "day"),
2156
2245
  month: windows.find((window2) => window2.period === "month"),
2157
2246
  blocked: data?.blocked ?? null,
2247
+ warning: data === void 0 || data.blocked !== void 0 ? null : data.warning ?? quotaWarning(windows) ?? null,
2158
2248
  isLoading,
2159
2249
  error,
2160
2250
  refresh
@@ -3243,7 +3333,9 @@ function readElicitationOutcome(part) {
3243
3333
  answers,
3244
3334
  skipped: output.skipped,
3245
3335
  defaulted: Array.isArray(output.defaulted) ? output.defaulted.filter((id) => typeof id === "string") : [],
3246
- 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
3247
3339
  };
3248
3340
  }
3249
3341
  __name(readElicitationOutcome, "readElicitationOutcome");
@@ -5018,13 +5110,19 @@ function storedMessageToUiMessage(message) {
5018
5110
  for (const call of message.toolCalls ?? []) {
5019
5111
  const result = message.toolResults?.find((candidate) => candidate.id === call.id);
5020
5112
  const toolKind2 = call.kind;
5113
+ const toolMetadata = {
5114
+ ...toolKind2 !== void 0 ? {
5115
+ toolKind: toolKind2
5116
+ } : {},
5117
+ ...call.parentId !== void 0 ? {
5118
+ parentId: call.parentId
5119
+ } : {}
5120
+ };
5021
5121
  parts.push({
5022
5122
  type: `tool-${call.name}`,
5023
5123
  toolCallId: call.id,
5024
- ...toolKind2 !== void 0 ? {
5025
- toolMetadata: {
5026
- toolKind: toolKind2
5027
- }
5124
+ ...Object.keys(toolMetadata).length > 0 ? {
5125
+ toolMetadata
5028
5126
  } : {},
5029
5127
  ...result !== void 0 ? isRefusal(result) ? {
5030
5128
  // A declined action is NOT an available output. Reloading a thread used to bring one
@@ -5052,6 +5150,8 @@ function storedMessageToUiMessage(message) {
5052
5150
  parts.push(...pushedByCall.get(call.id) ?? []);
5053
5151
  }
5054
5152
  const metadata = {
5153
+ // The host's own facts first: the library's keys win on a clash.
5154
+ ...message.metadata,
5055
5155
  ...message.feedback !== void 0 ? {
5056
5156
  feedback: message.feedback
5057
5157
  } : {},
@@ -5111,6 +5211,8 @@ function mergeTurn(rows) {
5111
5211
  const feedback = lastRow.feedback;
5112
5212
  const createdAt = rows[0]?.createdAt;
5113
5213
  const metadata = {
5214
+ // Host facts of every row, a later row's winning (the turn's final state), under the library's.
5215
+ ...Object.assign({}, ...rows.map((row) => row.metadata ?? {})),
5114
5216
  ...usage !== void 0 ? {
5115
5217
  usage
5116
5218
  } : {},
@@ -5183,6 +5285,7 @@ function useAgentChat(options = {}) {
5183
5285
  const [pickedModel, setPickedModel] = useState15(void 0);
5184
5286
  const pickedModelRef = useRef12(pickedModel);
5185
5287
  pickedModelRef.current = pickedModel;
5288
+ const lockedRef = useRef12(false);
5186
5289
  const pendingPin = useRef12(void 0);
5187
5290
  const regenerateNext = useRef12(false);
5188
5291
  const currentThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
@@ -5230,8 +5333,13 @@ function useAgentChat(options = {}) {
5230
5333
  const regenerate2 = regenerateNext.current;
5231
5334
  regenerateNext.current = false;
5232
5335
  const threadId = currentThreadId();
5233
- const model = current.model ?? pickedModelRef.current;
5336
+ const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
5234
5337
  return {
5338
+ // Read per send: a host that switches agents before the first message (a picker on a
5339
+ // new chat) sends the one picked now, not the one this chat mounted with.
5340
+ ...current.agent !== void 0 ? {
5341
+ agent: current.agent
5342
+ } : {},
5235
5343
  ...threadId !== void 0 ? {
5236
5344
  threadId
5237
5345
  } : {},
@@ -5319,6 +5427,7 @@ function useAgentChat(options = {}) {
5319
5427
  setRunId(void 0);
5320
5428
  setActiveRunId(null);
5321
5429
  setThreadModel(null);
5430
+ setPickedModel(void 0);
5322
5431
  setBackgroundRuns([]);
5323
5432
  setHistoryError(null);
5324
5433
  autoResumeRunIdRef.current = void 0;
@@ -5483,19 +5592,25 @@ function useAgentChat(options = {}) {
5483
5592
  }, [
5484
5593
  client
5485
5594
  ]);
5486
- const answer = useCallback11(async ({ toolCallId, answers }) => {
5595
+ const answer = useCallback11(async ({ toolCallId, answers, via }) => {
5487
5596
  await requireBackendMethod(client, "answerToolCall")({
5488
5597
  toolCallId,
5489
5598
  ...answers !== void 0 ? {
5490
5599
  answers
5600
+ } : {},
5601
+ ...via !== void 0 ? {
5602
+ via
5491
5603
  } : {}
5492
5604
  });
5493
5605
  }, [
5494
5606
  client
5495
5607
  ]);
5496
- const skip = useCallback11(async ({ toolCallId }) => {
5608
+ const skip = useCallback11(async ({ toolCallId, via }) => {
5497
5609
  await requireBackendMethod(client, "skipToolCall")({
5498
- toolCallId
5610
+ toolCallId,
5611
+ ...via !== void 0 ? {
5612
+ via
5613
+ } : {}
5499
5614
  });
5500
5615
  }, [
5501
5616
  client
@@ -5575,10 +5690,14 @@ function useAgentChat(options = {}) {
5575
5690
  modelsWantedRef.current = true;
5576
5691
  queueMicrotask(() => setModelsWanted(true));
5577
5692
  }, []);
5578
- const selectModel = useCallback11((id) => setPickedModel(id), []);
5693
+ lockedRef.current = catalog.locked !== null;
5694
+ const selectModel = useCallback11((id) => {
5695
+ if (!lockedRef.current) setPickedModel(id);
5696
+ }, []);
5579
5697
  const pinToThread = useCallback11(async (model) => {
5580
5698
  const threadId = currentThreadId();
5581
5699
  setThreadModel(model);
5700
+ setPickedModel(void 0);
5582
5701
  if (threadId === void 0) {
5583
5702
  pendingPin.current = {
5584
5703
  model
@@ -5604,7 +5723,16 @@ function useAgentChat(options = {}) {
5604
5723
  wantModels();
5605
5724
  return catalog.providers;
5606
5725
  },
5607
- selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5726
+ selected: catalog.locked?.model ?? options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5727
+ pinned: threadModel,
5728
+ get defaultModel() {
5729
+ wantModels();
5730
+ return catalog.defaultModel;
5731
+ },
5732
+ get locked() {
5733
+ wantModels();
5734
+ return catalog.locked;
5735
+ },
5608
5736
  select: selectModel,
5609
5737
  pinToThread,
5610
5738
  isLoading: modelsWanted && catalog.isLoading,
@@ -5632,10 +5760,26 @@ function useAgentChat(options = {}) {
5632
5760
  const current = composerRef.current;
5633
5761
  if (current.blockedBy !== null) return;
5634
5762
  const refs = current.files.refs;
5763
+ const files2 = current.files.items.flatMap((item) => item.status === "ready" && item.attachment !== void 0 ? [
5764
+ {
5765
+ type: "file",
5766
+ mediaType: item.attachment.contentType,
5767
+ filename: item.attachment.name,
5768
+ url: item.attachment.url,
5769
+ providerMetadata: {
5770
+ agent: {
5771
+ mediaId: item.attachment.mediaId
5772
+ }
5773
+ }
5774
+ }
5775
+ ] : []);
5635
5776
  const draft = current.text.trim();
5636
5777
  setText("");
5637
5778
  current.files.clear();
5638
- await sendMessage({
5779
+ await sendMessage(files2.length > 0 ? {
5780
+ text: draft,
5781
+ files: files2
5782
+ } : {
5639
5783
  text: draft
5640
5784
  }, refs.length > 0 ? {
5641
5785
  body: {