@dudousxd/nestjs-agent-react 0.24.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.cjs CHANGED
@@ -99,22 +99,87 @@ __export(src_exports, {
99
99
  useThreads: () => useThreads,
100
100
  useToolCatalog: () => useToolCatalog,
101
101
  useTranscriptItem: () => useTranscriptItem,
102
- validateAnswer: () => import_nestjs_agent_core.validateElicitationAnswer,
103
- validateAnswerValue: () => import_nestjs_agent_core.validateElicitationValue
102
+ validateAnswer: () => import_nestjs_agent_core2.validateElicitationAnswer,
103
+ validateAnswerValue: () => import_nestjs_agent_core2.validateElicitationValue
104
104
  });
105
105
  module.exports = __toCommonJS(src_exports);
106
106
 
107
+ // src/http-error.ts
108
+ function readErrorAnswer(text) {
109
+ if (text == null || text === "") return {
110
+ body: void 0,
111
+ message: void 0,
112
+ code: void 0
113
+ };
114
+ let body = text;
115
+ try {
116
+ body = JSON.parse(text);
117
+ } catch {
118
+ return {
119
+ body: text,
120
+ message: void 0,
121
+ code: void 0
122
+ };
123
+ }
124
+ if (body === null || typeof body !== "object") return {
125
+ body,
126
+ message: void 0,
127
+ code: void 0
128
+ };
129
+ const record = body;
130
+ 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;
131
+ const code = typeof record.code === "string" && record.code !== "" ? record.code : void 0;
132
+ return {
133
+ body,
134
+ message,
135
+ code
136
+ };
137
+ }
138
+ __name(readErrorAnswer, "readErrorAnswer");
139
+ async function readErrorResponse(response) {
140
+ let text;
141
+ try {
142
+ text = await response.text();
143
+ } catch {
144
+ text = void 0;
145
+ }
146
+ return readErrorAnswer(text);
147
+ }
148
+ __name(readErrorResponse, "readErrorResponse");
149
+ function reportHttpError(listener, error) {
150
+ if (listener === void 0) return;
151
+ try {
152
+ listener(error);
153
+ } catch {
154
+ }
155
+ }
156
+ __name(reportHttpError, "reportHttpError");
157
+
107
158
  // src/client.ts
108
- var AgentHttpError = class extends Error {
159
+ var AgentHttpError = class _AgentHttpError extends Error {
109
160
  static {
110
161
  __name(this, "AgentHttpError");
111
162
  }
112
163
  status;
113
164
  method;
114
165
  path;
115
- constructor(status, method, path, statusText) {
116
- super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
166
+ /** The answer's body: parsed JSON, else its text; `undefined` when empty. */
167
+ body;
168
+ /** The body's `code` (`quota_exceeded`, …), when the server sent one. */
169
+ code;
170
+ constructor(status, method, path, statusText, answer = {
171
+ body: void 0,
172
+ message: void 0,
173
+ code: void 0
174
+ }) {
175
+ super(answer.message ?? `Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
117
176
  this.name = "AgentHttpError";
177
+ this.body = answer.body;
178
+ this.code = answer.code;
179
+ }
180
+ /** Read a refused `response`'s body into an error. */
181
+ static async from(response, method, path) {
182
+ return new _AgentHttpError(response.status, method, path, response.statusText, await readErrorResponse(response));
118
183
  }
119
184
  };
120
185
  function normalizeAgentPath(path) {
@@ -149,7 +214,7 @@ var AgentClient = class {
149
214
  } : {}
150
215
  });
151
216
  if (!response.ok || !response.body) {
152
- throw new AgentHttpError(response.status, "POST", `${this.agentPath()}/chat`, response.statusText);
217
+ throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
153
218
  }
154
219
  return streamResponse(response);
155
220
  }
@@ -176,7 +241,7 @@ var AgentClient = class {
176
241
  return null;
177
242
  }
178
243
  if (!response.ok || !response.body) {
179
- throw new AgentHttpError(response.status, "GET", `${this.agentPath()}${path}`, response.statusText);
244
+ throw await this.failure(response, "GET", `${this.agentPath()}${path}`);
180
245
  }
181
246
  return streamResponse(response);
182
247
  }
@@ -269,7 +334,9 @@ var AgentClient = class {
269
334
  };
270
335
  xhr.onload = () => {
271
336
  if (xhr.status < 200 || xhr.status >= 300) {
272
- reject(new AgentHttpError(xhr.status, "POST", `${this.agentPath()}/attachments`, xhr.statusText));
337
+ const error = new AgentHttpError(xhr.status, "POST", `${this.agentPath()}/attachments`, xhr.statusText, readErrorAnswer(xhr.responseText));
338
+ reportHttpError(this.options.onHttpError, error);
339
+ reject(error);
273
340
  return;
274
341
  }
275
342
  onProgress?.(1);
@@ -332,6 +399,7 @@ var AgentClient = class {
332
399
  * out takes the pre-picked default the request carried, resolved server-side against the request
333
400
  * the run already holds. Omit the whole object and the user has confirmed every pre-picked
334
401
  * answer — which is the point of the surface, so it is a valid submission rather than a blank.
402
+ * `via` names the surface the answer came through (the server records `'web'` when omitted).
335
403
  */
336
404
  answerToolCall(input) {
337
405
  return this.request("POST", "/tool-call/answer", input);
@@ -354,7 +422,10 @@ var AgentClient = class {
354
422
  path: this.agentPath(),
355
423
  headers: /* @__PURE__ */ __name(() => this.resolveHeaders(), "headers"),
356
424
  fetch: this.fetchImpl(),
357
- ...this.credentials()
425
+ ...this.credentials(),
426
+ ...this.options.onHttpError !== void 0 ? {
427
+ onHttpError: this.options.onHttpError
428
+ } : {}
358
429
  };
359
430
  }
360
431
  baseUrl() {
@@ -397,9 +468,15 @@ var AgentClient = class {
397
468
  });
398
469
  return this.handleResponse(response, method, path);
399
470
  }
471
+ /** The error for a refused `response`, already reported to `onHttpError`. */
472
+ async failure(response, method, path) {
473
+ const error = await AgentHttpError.from(response, method, path);
474
+ reportHttpError(this.options.onHttpError, error);
475
+ return error;
476
+ }
400
477
  async handleResponse(response, method, path) {
401
478
  if (!response.ok) {
402
- throw new AgentHttpError(response.status, method, path, response.statusText);
479
+ throw await this.failure(response, method, path);
403
480
  }
404
481
  if (response.status === 204) return void 0;
405
482
  const text = await response.text();
@@ -1754,14 +1831,16 @@ __name(GenerativeUI, "GenerativeUI");
1754
1831
 
1755
1832
  // src/provider.tsx
1756
1833
  var AgentBackendContext = sharedContext("@dudousxd/nestjs-agent-react:agent-backend");
1757
- function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credentials, fetch, attachments, genui, children }) {
1834
+ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credentials, fetch, onHttpError, attachments, genui, children }) {
1758
1835
  const latestHeaders = (0, import_react5.useRef)({
1759
1836
  headers,
1760
- getHeaders
1837
+ getHeaders,
1838
+ onHttpError
1761
1839
  });
1762
1840
  latestHeaders.current = {
1763
1841
  headers,
1764
- getHeaders
1842
+ getHeaders,
1843
+ onHttpError
1765
1844
  };
1766
1845
  const upload = attachments?.upload;
1767
1846
  const value = (0, import_react5.useMemo)(() => backend ?? new AgentClient({
@@ -1782,6 +1861,7 @@ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credential
1782
1861
  upload
1783
1862
  }
1784
1863
  } : {},
1864
+ onHttpError: /* @__PURE__ */ __name((error) => latestHeaders.current.onHttpError?.(error), "onHttpError"),
1785
1865
  getHeaders: /* @__PURE__ */ __name(async () => {
1786
1866
  const current = latestHeaders.current;
1787
1867
  const dynamic = await current.getHeaders?.() ?? {};
@@ -2195,6 +2275,7 @@ function useModels(options = {}) {
2195
2275
  providers,
2196
2276
  models,
2197
2277
  defaultModel: data?.default ?? null,
2278
+ locked: data?.locked ?? null,
2198
2279
  find: /* @__PURE__ */ __name((id) => id == null ? void 0 : byId.get(id), "find"),
2199
2280
  isLoading,
2200
2281
  error,
@@ -2210,6 +2291,7 @@ function useModels(options = {}) {
2210
2291
  __name(useModels, "useModels");
2211
2292
 
2212
2293
  // src/quota/use-quota.ts
2294
+ var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
2213
2295
  var import_react11 = require("react");
2214
2296
 
2215
2297
  // src/threads/threads-events.ts
@@ -2265,6 +2347,7 @@ function useQuota(options = {}) {
2265
2347
  day: windows.find((window2) => window2.period === "day"),
2266
2348
  month: windows.find((window2) => window2.period === "month"),
2267
2349
  blocked: data?.blocked ?? null,
2350
+ warning: data === void 0 || data.blocked !== void 0 ? null : data.warning ?? (0, import_nestjs_agent_core.quotaWarning)(windows) ?? null,
2268
2351
  isLoading,
2269
2352
  error,
2270
2353
  refresh
@@ -2708,11 +2791,11 @@ var import_react18 = __toESM(require("react"), 1);
2708
2791
  var import_react17 = require("react");
2709
2792
 
2710
2793
  // src/transcript/model.ts
2711
- var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
2794
+ var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
2712
2795
  var import_ai2 = require("ai");
2713
2796
 
2714
2797
  // src/elicitation/answers.ts
2715
- var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
2798
+ var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
2716
2799
  function pad(value) {
2717
2800
  return String(value).padStart(2, "0");
2718
2801
  }
@@ -3307,7 +3390,7 @@ function readElicitationRequest(part) {
3307
3390
  if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
3308
3391
  return null;
3309
3392
  }
3310
- const input2 = (0, import_nestjs_agent_core2.readElicitationInput)(candidate.input) ?? null;
3393
+ const input2 = (0, import_nestjs_agent_core3.readElicitationInput)(candidate.input) ?? null;
3311
3394
  const rawOptions = Array.isArray(candidate.options) ? candidate.options : [];
3312
3395
  if (rawOptions.length === 0 && (input2 === null || input2.type === "select")) {
3313
3396
  return null;
@@ -3353,7 +3436,9 @@ function readElicitationOutcome(part) {
3353
3436
  answers,
3354
3437
  skipped: output.skipped,
3355
3438
  defaulted: Array.isArray(output.defaulted) ? output.defaulted.filter((id) => typeof id === "string") : [],
3356
- summary: typeof output.summary === "string" ? output.summary : null
3439
+ summary: typeof output.summary === "string" ? output.summary : null,
3440
+ answeredBy: typeof output.answeredBy === "string" ? output.answeredBy : null,
3441
+ answeredVia: typeof output.answeredVia === "string" ? output.answeredVia : null
3357
3442
  };
3358
3443
  }
3359
3444
  __name(readElicitationOutcome, "readElicitationOutcome");
@@ -3365,7 +3450,7 @@ function buildElicitationBlock(key, part, request, options) {
3365
3450
  const picked = isPending ? options.picked(toolCallId, question.id) : void 0;
3366
3451
  const selected = picked ?? outcome?.answers[question.id] ?? question.defaults;
3367
3452
  const pick = /* @__PURE__ */ __name((values) => options.pick(toolCallId, question.id, values), "pick");
3368
- const error = isPending ? (0, import_nestjs_agent_core2.validateElicitationAnswer)({
3453
+ const error = isPending ? (0, import_nestjs_agent_core3.validateElicitationAnswer)({
3369
3454
  id: question.id,
3370
3455
  prompt: question.prompt,
3371
3456
  options: question.options.map(({ value, label }) => ({
@@ -5014,7 +5099,7 @@ function useApprovalCountdown(expiresAt, options = {}) {
5014
5099
  __name(useApprovalCountdown, "useApprovalCountdown");
5015
5100
 
5016
5101
  // src/stored-message-to-ui-message.ts
5017
- var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
5102
+ var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
5018
5103
  function isRefusal(result) {
5019
5104
  if (result.denied === true) {
5020
5105
  return true;
@@ -5027,7 +5112,7 @@ function refusalReason(result) {
5027
5112
  const { output } = result;
5028
5113
  if (output !== null && typeof output === "object" && "reason" in output) {
5029
5114
  const reason = output.reason;
5030
- return typeof reason === "string" && reason !== import_nestjs_agent_core3.DEFAULT_REFUSAL_REASON ? reason : void 0;
5115
+ return typeof reason === "string" && reason !== import_nestjs_agent_core4.DEFAULT_REFUSAL_REASON ? reason : void 0;
5031
5116
  }
5032
5117
  return void 0;
5033
5118
  }
@@ -5303,6 +5388,7 @@ function useAgentChat(options = {}) {
5303
5388
  const [pickedModel, setPickedModel] = (0, import_react23.useState)(void 0);
5304
5389
  const pickedModelRef = (0, import_react23.useRef)(pickedModel);
5305
5390
  pickedModelRef.current = pickedModel;
5391
+ const lockedRef = (0, import_react23.useRef)(false);
5306
5392
  const pendingPin = (0, import_react23.useRef)(void 0);
5307
5393
  const regenerateNext = (0, import_react23.useRef)(false);
5308
5394
  const currentThreadId = (0, import_react23.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
@@ -5350,7 +5436,7 @@ function useAgentChat(options = {}) {
5350
5436
  const regenerate2 = regenerateNext.current;
5351
5437
  regenerateNext.current = false;
5352
5438
  const threadId = currentThreadId();
5353
- const model = current.model ?? pickedModelRef.current;
5439
+ const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
5354
5440
  return {
5355
5441
  // Read per send: a host that switches agents before the first message (a picker on a
5356
5442
  // new chat) sends the one picked now, not the one this chat mounted with.
@@ -5444,6 +5530,7 @@ function useAgentChat(options = {}) {
5444
5530
  setRunId(void 0);
5445
5531
  setActiveRunId(null);
5446
5532
  setThreadModel(null);
5533
+ setPickedModel(void 0);
5447
5534
  setBackgroundRuns([]);
5448
5535
  setHistoryError(null);
5449
5536
  autoResumeRunIdRef.current = void 0;
@@ -5608,19 +5695,25 @@ function useAgentChat(options = {}) {
5608
5695
  }, [
5609
5696
  client
5610
5697
  ]);
5611
- const answer = (0, import_react23.useCallback)(async ({ toolCallId, answers }) => {
5698
+ const answer = (0, import_react23.useCallback)(async ({ toolCallId, answers, via }) => {
5612
5699
  await requireBackendMethod(client, "answerToolCall")({
5613
5700
  toolCallId,
5614
5701
  ...answers !== void 0 ? {
5615
5702
  answers
5703
+ } : {},
5704
+ ...via !== void 0 ? {
5705
+ via
5616
5706
  } : {}
5617
5707
  });
5618
5708
  }, [
5619
5709
  client
5620
5710
  ]);
5621
- const skip = (0, import_react23.useCallback)(async ({ toolCallId }) => {
5711
+ const skip = (0, import_react23.useCallback)(async ({ toolCallId, via }) => {
5622
5712
  await requireBackendMethod(client, "skipToolCall")({
5623
- toolCallId
5713
+ toolCallId,
5714
+ ...via !== void 0 ? {
5715
+ via
5716
+ } : {}
5624
5717
  });
5625
5718
  }, [
5626
5719
  client
@@ -5700,10 +5793,14 @@ function useAgentChat(options = {}) {
5700
5793
  modelsWantedRef.current = true;
5701
5794
  queueMicrotask(() => setModelsWanted(true));
5702
5795
  }, []);
5703
- const selectModel = (0, import_react23.useCallback)((id) => setPickedModel(id), []);
5796
+ lockedRef.current = catalog.locked !== null;
5797
+ const selectModel = (0, import_react23.useCallback)((id) => {
5798
+ if (!lockedRef.current) setPickedModel(id);
5799
+ }, []);
5704
5800
  const pinToThread = (0, import_react23.useCallback)(async (model) => {
5705
5801
  const threadId = currentThreadId();
5706
5802
  setThreadModel(model);
5803
+ setPickedModel(void 0);
5707
5804
  if (threadId === void 0) {
5708
5805
  pendingPin.current = {
5709
5806
  model
@@ -5729,11 +5826,16 @@ function useAgentChat(options = {}) {
5729
5826
  wantModels();
5730
5827
  return catalog.providers;
5731
5828
  },
5732
- selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5829
+ selected: catalog.locked?.model ?? options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5830
+ pinned: threadModel,
5733
5831
  get defaultModel() {
5734
5832
  wantModels();
5735
5833
  return catalog.defaultModel;
5736
5834
  },
5835
+ get locked() {
5836
+ wantModels();
5837
+ return catalog.locked;
5838
+ },
5737
5839
  select: selectModel,
5738
5840
  pinToThread,
5739
5841
  isLoading: modelsWanted && catalog.isLoading,