@dudousxd/nestjs-agent-react 0.29.1 → 0.30.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
@@ -569,6 +569,7 @@ var AgentChatTransport = class {
569
569
  return null;
570
570
  }
571
571
  this.captureHeaderMeta(response);
572
+ this.options.onResumeAttached?.(response.runId ?? runId);
572
573
  return this.toChunkStream(response, {
573
574
  runId,
574
575
  ...headers !== void 0 ? { headers } : {}
@@ -1820,6 +1821,282 @@ function useAttachments(options = {}) {
1820
1821
  }, [items, add, remove, retry, clear, inputProps, dropZoneProps, dragDepth, onPaste]);
1821
1822
  }
1822
1823
 
1824
+ // src/ag-ui-backend.ts
1825
+ function newId() {
1826
+ return globalThis.crypto.randomUUID();
1827
+ }
1828
+ function isRecord2(value) {
1829
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1830
+ }
1831
+ async function defaultRefusal(response) {
1832
+ try {
1833
+ const body = await response.json();
1834
+ if (typeof body.message === "string" && body.message.length > 0) return new Error(body.message);
1835
+ } catch {
1836
+ }
1837
+ return new Error(`The agent refused the request (${response.status}).`);
1838
+ }
1839
+ function defaultForwardedProps(body) {
1840
+ const out = {};
1841
+ for (const key of ["pageContext", "agent", "model"]) {
1842
+ if (body[key] !== void 0 && body[key] !== null) out[key] = body[key];
1843
+ }
1844
+ return Object.keys(out).length > 0 ? out : void 0;
1845
+ }
1846
+ async function agUiChatStream(request, options) {
1847
+ const body = request.body;
1848
+ const threadId = typeof body.threadId === "string" ? body.threadId : newId();
1849
+ const message = typeof body.message === "string" ? body.message : "";
1850
+ const forwarded = (options.forwardedProps ?? defaultForwardedProps)(body);
1851
+ const input = {
1852
+ threadId,
1853
+ runId: newId(),
1854
+ protocolVersion: "1.0",
1855
+ messages: [{ id: newId(), role: "user", content: message }],
1856
+ ...forwarded !== void 0 ? { forwardedProps: forwarded } : {}
1857
+ };
1858
+ const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
1859
+ const response = await doFetch(options.url, {
1860
+ method: "POST",
1861
+ credentials: "same-origin",
1862
+ headers: {
1863
+ "content-type": "application/json",
1864
+ accept: "text/event-stream",
1865
+ ...options.headers,
1866
+ ...request.headers
1867
+ },
1868
+ body: JSON.stringify(input),
1869
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
1870
+ });
1871
+ if (!response.ok || response.body === null) {
1872
+ throw await (options.refusal ?? defaultRefusal)(response);
1873
+ }
1874
+ const runId = response.headers.get("x-agent-run-id") ?? void 0;
1875
+ return {
1876
+ body: reframeAgUiStream(response.body, { threadId }),
1877
+ threadId,
1878
+ ...runId !== void 0 ? { runId } : {}
1879
+ };
1880
+ }
1881
+ function reframeAgUiStream(source, ids) {
1882
+ const encoder = new TextEncoder();
1883
+ const decoder = new TextDecoder();
1884
+ const reframer = createReframer(ids.threadId);
1885
+ const reader = source.getReader();
1886
+ let buffer = "";
1887
+ return new ReadableStream({
1888
+ async pull(controller) {
1889
+ for (; ; ) {
1890
+ const { value, done } = await reader.read();
1891
+ if (done) {
1892
+ controller.close();
1893
+ return;
1894
+ }
1895
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, "\n");
1896
+ let out = "";
1897
+ let separator = buffer.indexOf("\n\n");
1898
+ while (separator !== -1) {
1899
+ const block = buffer.slice(0, separator);
1900
+ buffer = buffer.slice(separator + 2);
1901
+ separator = buffer.indexOf("\n\n");
1902
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
1903
+ if (data.length === 0) continue;
1904
+ let event;
1905
+ try {
1906
+ event = JSON.parse(data);
1907
+ } catch {
1908
+ continue;
1909
+ }
1910
+ if (isRecord2(event) && typeof event.type === "string") {
1911
+ out += reframer.push(event);
1912
+ }
1913
+ if (reframer.closed) break;
1914
+ }
1915
+ if (out.length > 0) controller.enqueue(encoder.encode(out));
1916
+ if (reframer.closed) {
1917
+ void reader.cancel().catch(() => void 0);
1918
+ controller.close();
1919
+ return;
1920
+ }
1921
+ if (out.length > 0) return;
1922
+ }
1923
+ },
1924
+ cancel(reason) {
1925
+ return reader.cancel(reason).catch(() => void 0);
1926
+ }
1927
+ });
1928
+ }
1929
+ function createReframer(threadId) {
1930
+ let seq = 0;
1931
+ let metaSent = false;
1932
+ const tools = /* @__PURE__ */ new Map();
1933
+ const state = {
1934
+ closed: false,
1935
+ push(event) {
1936
+ if (state.closed) return "";
1937
+ let out = "";
1938
+ const frame = (streamEvent) => {
1939
+ seq += 1;
1940
+ out += `id: ${seq}
1941
+ data: ${JSON.stringify(streamEvent)}
1942
+
1943
+ `;
1944
+ };
1945
+ const meta = (runId, thread) => {
1946
+ if (metaSent) return;
1947
+ metaSent = true;
1948
+ out += `event: meta
1949
+ data: ${JSON.stringify({
1950
+ runId: typeof runId === "string" ? runId : "",
1951
+ threadId: typeof thread === "string" ? thread : threadId
1952
+ })}
1953
+
1954
+ `;
1955
+ };
1956
+ switch (event.type) {
1957
+ case "RUN_STARTED":
1958
+ pendingStart = { runId: event.runId, threadId: event.threadId };
1959
+ return out;
1960
+ case "CUSTOM": {
1961
+ const value = event.value;
1962
+ if (event.name === "agora.run" && isRecord2(value)) {
1963
+ meta(value.runId, pendingStart?.threadId ?? value.threadId);
1964
+ pendingStart = void 0;
1965
+ return out;
1966
+ }
1967
+ flushStart();
1968
+ if (!isRecord2(value)) return out;
1969
+ if (event.name === "agora.ui") frame({ kind: "ui", ...value });
1970
+ else if (event.name === "agora.title" && typeof value.title === "string") {
1971
+ frame({ kind: "title", title: value.title });
1972
+ } else if (event.name === "agora.queue") frame({ kind: "queue", ...value });
1973
+ else if (event.name === "agora.approval-requested") {
1974
+ frame({ ...value, kind: "approval-requested" });
1975
+ } else if (event.name === "agora.approval-settled") {
1976
+ frame({ ...value, kind: "approval-settled" });
1977
+ }
1978
+ return out;
1979
+ }
1980
+ default:
1981
+ flushStart();
1982
+ }
1983
+ switch (event.type) {
1984
+ case "TEXT_MESSAGE_CONTENT":
1985
+ case "TEXT_MESSAGE_CHUNK":
1986
+ if (typeof event.delta === "string" && event.delta.length > 0) {
1987
+ frame({ kind: "text", text: event.delta });
1988
+ }
1989
+ break;
1990
+ case "REASONING_MESSAGE_CONTENT":
1991
+ case "REASONING_MESSAGE_CHUNK":
1992
+ if (typeof event.delta === "string" && event.delta.length > 0) {
1993
+ frame({ kind: "reasoning", text: event.delta });
1994
+ }
1995
+ break;
1996
+ case "STEP_STARTED":
1997
+ frame({ kind: "step-start" });
1998
+ break;
1999
+ case "STEP_FINISHED":
2000
+ frame({ kind: "step-finish" });
2001
+ break;
2002
+ case "TOOL_CALL_START":
2003
+ case "TOOL_CALL_CHUNK": {
2004
+ const id = String(event.toolCallId ?? "");
2005
+ if (id === "") break;
2006
+ if (!tools.has(id)) {
2007
+ const name = typeof event.toolCallName === "string" ? event.toolCallName : "";
2008
+ tools.set(id, { name, args: "" });
2009
+ frame({ kind: "tool-input-start", id, name, toolKind: "read" });
2010
+ }
2011
+ if (event.type === "TOOL_CALL_CHUNK" && typeof event.delta === "string") {
2012
+ tools.get(id).args += event.delta;
2013
+ frame({ kind: "tool-input-delta", id, delta: event.delta });
2014
+ }
2015
+ break;
2016
+ }
2017
+ case "TOOL_CALL_ARGS": {
2018
+ const call = tools.get(String(event.toolCallId));
2019
+ if (call === void 0 || typeof event.delta !== "string") break;
2020
+ call.args += event.delta;
2021
+ frame({ kind: "tool-input-delta", id: String(event.toolCallId), delta: event.delta });
2022
+ break;
2023
+ }
2024
+ case "TOOL_CALL_END": {
2025
+ const id = String(event.toolCallId);
2026
+ const call = tools.get(id);
2027
+ if (call === void 0) break;
2028
+ frame({
2029
+ kind: "tool-input-available",
2030
+ id,
2031
+ name: call.name,
2032
+ input: parseJson(call.args, {}),
2033
+ toolKind: "read"
2034
+ });
2035
+ break;
2036
+ }
2037
+ case "TOOL_CALL_RESULT": {
2038
+ const id = String(event.toolCallId);
2039
+ const content = contentText(event.content);
2040
+ const outcome = isRecord2(event.metadata) ? event.metadata["agora.outcome"] : void 0;
2041
+ if (outcome === "error") frame({ kind: "tool-output-error", id, error: content });
2042
+ else if (outcome === "denied") frame({ kind: "tool-output-denied", id, reason: content });
2043
+ else frame({ kind: "tool-output", id, output: parseJson(content, content) });
2044
+ break;
2045
+ }
2046
+ case "RUN_ERROR":
2047
+ state.closed = true;
2048
+ out += `event: error
2049
+ data: ${JSON.stringify({
2050
+ code: typeof event.code === "string" ? event.code : "run_failed",
2051
+ message: typeof event.message === "string" ? event.message : "The run failed."
2052
+ })}
2053
+
2054
+ `;
2055
+ break;
2056
+ case "RUN_FINISHED": {
2057
+ const outcome = isRecord2(event.outcome) ? event.outcome : void 0;
2058
+ if (outcome?.type === "cancelled") frame({ kind: "cancelled" });
2059
+ if (outcome?.type === "interrupt" && Array.isArray(outcome.interrupts)) {
2060
+ frame({
2061
+ kind: "ui",
2062
+ id: `ag-ui:interrupt:${String(event.runId ?? "")}`,
2063
+ component: "AgUiInterrupt",
2064
+ props: { interrupts: outcome.interrupts }
2065
+ });
2066
+ }
2067
+ state.closed = true;
2068
+ out += "event: done\ndata: {}\n\n";
2069
+ break;
2070
+ }
2071
+ default:
2072
+ break;
2073
+ }
2074
+ return out;
2075
+ function flushStart() {
2076
+ if (pendingStart !== void 0) {
2077
+ meta(pendingStart.runId, pendingStart.threadId);
2078
+ pendingStart = void 0;
2079
+ }
2080
+ }
2081
+ }
2082
+ };
2083
+ let pendingStart;
2084
+ return state;
2085
+ }
2086
+ function parseJson(text, fallback2) {
2087
+ if (text.trim().length === 0) return fallback2;
2088
+ try {
2089
+ return JSON.parse(text);
2090
+ } catch {
2091
+ return fallback2;
2092
+ }
2093
+ }
2094
+ function contentText(content) {
2095
+ if (typeof content === "string") return content;
2096
+ if (!Array.isArray(content)) return "";
2097
+ return content.filter((part) => isRecord2(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
2098
+ }
2099
+
1823
2100
  // src/catalog/use-agents.ts
1824
2101
  import { useMemo as useMemo5 } from "react";
1825
2102
 
@@ -2472,7 +2749,7 @@ function phraseFor(presentation, input, isSettled2, fallback2 = { running: "Work
2472
2749
  }
2473
2750
 
2474
2751
  // src/presentation/result-view.ts
2475
- function isRecord2(value) {
2752
+ function isRecord3(value) {
2476
2753
  return typeof value === "object" && value !== null && !Array.isArray(value);
2477
2754
  }
2478
2755
  function toText(value) {
@@ -2484,18 +2761,18 @@ function toText(value) {
2484
2761
  return "\u2014";
2485
2762
  }
2486
2763
  function inferResultView(output) {
2487
- if (!isRecord2(output)) return void 0;
2764
+ if (!isRecord3(output)) return void 0;
2488
2765
  for (const [key, value] of Object.entries(output)) {
2489
2766
  if (!Array.isArray(value) || value.length === 0) continue;
2490
2767
  if (value.every((entry) => typeof entry === "string")) {
2491
2768
  return { kind: "log", lines: key };
2492
2769
  }
2493
- if (value.every(isRecord2)) {
2770
+ if (value.every(isRecord3)) {
2494
2771
  const first = value[0];
2495
2772
  return {
2496
2773
  kind: "table",
2497
2774
  rows: key,
2498
- columns: Object.keys(first).filter((column) => !isRecord2(first[column])).map((column) => ({ path: column, label: column }))
2775
+ columns: Object.keys(first).filter((column) => !isRecord3(first[column])).map((column) => ({ path: column, label: column }))
2499
2776
  };
2500
2777
  }
2501
2778
  }
@@ -2822,7 +3099,7 @@ function isSettled(state) {
2822
3099
  }
2823
3100
  function toolKind(part) {
2824
3101
  const metadata = part.toolMetadata;
2825
- return isRecord3(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
3102
+ return isRecord4(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
2826
3103
  }
2827
3104
  function isAwaitingApproval(part) {
2828
3105
  if (part.state === "approval-requested") {
@@ -2835,7 +3112,7 @@ function isAwaitingApproval(part) {
2835
3112
  }
2836
3113
  function toolParentId(part) {
2837
3114
  const metadata = part.toolMetadata;
2838
- return isRecord3(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
3115
+ return isRecord4(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
2839
3116
  }
2840
3117
  function nestToolCalls(calls) {
2841
3118
  const byId = new Map(calls.map((call) => [call.toolCallId, call]));
@@ -2894,12 +3171,12 @@ function buildToolCall(part, options, approval, catalog) {
2894
3171
  }
2895
3172
  function readElicitationRequest(part) {
2896
3173
  const input = part.input;
2897
- if (!isRecord3(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
3174
+ if (!isRecord4(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
2898
3175
  return null;
2899
3176
  }
2900
3177
  const questions = [];
2901
3178
  for (const candidate of input.questions) {
2902
- if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
3179
+ if (!isRecord4(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
2903
3180
  return null;
2904
3181
  }
2905
3182
  const input2 = readElicitationInput(candidate.input) ?? null;
@@ -2909,7 +3186,7 @@ function readElicitationRequest(part) {
2909
3186
  }
2910
3187
  const options = [];
2911
3188
  for (const option of rawOptions) {
2912
- if (!isRecord3(option) || typeof option.value !== "string" || typeof option.label !== "string") {
3189
+ if (!isRecord4(option) || typeof option.value !== "string" || typeof option.label !== "string") {
2913
3190
  return null;
2914
3191
  }
2915
3192
  options.push({
@@ -2936,7 +3213,7 @@ function readElicitationRequest(part) {
2936
3213
  }
2937
3214
  function readElicitationOutcome(part) {
2938
3215
  const output = part.output;
2939
- if (!isRecord3(output) || !isRecord3(output.answers) || typeof output.skipped !== "boolean") {
3216
+ if (!isRecord4(output) || !isRecord4(output.answers) || typeof output.skipped !== "boolean") {
2940
3217
  return null;
2941
3218
  }
2942
3219
  const answers = {};
@@ -3061,7 +3338,7 @@ function readApprovals(parts) {
3061
3338
  continue;
3062
3339
  }
3063
3340
  const data = part.data;
3064
- if (!isRecord3(data) || typeof data.id !== "string") {
3341
+ if (!isRecord4(data) || typeof data.id !== "string") {
3065
3342
  continue;
3066
3343
  }
3067
3344
  const approval = entry(data.id);
@@ -3114,7 +3391,7 @@ function statusFromPart(call) {
3114
3391
  }
3115
3392
  function readUiComponent(part) {
3116
3393
  const data = part.data;
3117
- if (!isRecord3(data) || typeof data.component !== "string") {
3394
+ if (!isRecord4(data) || typeof data.component !== "string") {
3118
3395
  return null;
3119
3396
  }
3120
3397
  const id = typeof data.id === "string" ? data.id : part.id;
@@ -3124,29 +3401,29 @@ function readUiComponent(part) {
3124
3401
  return {
3125
3402
  id,
3126
3403
  component: data.component,
3127
- props: isRecord3(data.props) ? data.props : {},
3404
+ props: isRecord4(data.props) ? data.props : {},
3128
3405
  version: typeof data.version === "number" ? data.version : null,
3129
3406
  toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : null
3130
3407
  };
3131
3408
  }
3132
- function isRecord3(value) {
3409
+ function isRecord4(value) {
3133
3410
  return typeof value === "object" && value !== null;
3134
3411
  }
3135
3412
  function readQuery(part) {
3136
3413
  const input = part.input;
3137
- if (!isRecord3(input) || typeof input.query !== "string") {
3414
+ if (!isRecord4(input) || typeof input.query !== "string") {
3138
3415
  return null;
3139
3416
  }
3140
3417
  return input.query;
3141
3418
  }
3142
3419
  function readPassages(part) {
3143
3420
  const output = part.output;
3144
- if (!isRecord3(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
3421
+ if (!isRecord4(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
3145
3422
  return null;
3146
3423
  }
3147
3424
  const passages = [];
3148
3425
  for (const candidate of output.passages) {
3149
- if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
3426
+ if (!isRecord4(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
3150
3427
  return null;
3151
3428
  }
3152
3429
  passages.push({
@@ -3154,7 +3431,7 @@ function readPassages(part) {
3154
3431
  text: candidate.text,
3155
3432
  score: typeof candidate.score === "number" ? candidate.score : 0,
3156
3433
  ...typeof candidate.source === "string" ? { source: candidate.source } : {},
3157
- ...isRecord3(candidate.metadata) ? { metadata: candidate.metadata } : {}
3434
+ ...isRecord4(candidate.metadata) ? { metadata: candidate.metadata } : {}
3158
3435
  });
3159
3436
  }
3160
3437
  return passages;
@@ -4714,6 +4991,14 @@ function storedMessageToUiMessage(message) {
4714
4991
  approved: false,
4715
4992
  ...refusalReason(result) !== void 0 ? { reason: refusalReason(result) } : {}
4716
4993
  }
4994
+ } : typeof result.error === "string" && result.error !== "" ? {
4995
+ // A call that FAILED — the tool threw, its input was refused, or its turn died
4996
+ // before it was settled — is not an available output either. The live stream says
4997
+ // so with `tool-output-error`; a reloaded thread has to say the same, or the card of
4998
+ // an action that never ran comes back reading as one that completed.
4999
+ state: "output-error",
5000
+ input: call.input,
5001
+ errorText: result.error
4717
5002
  } : { state: "output-available", input: call.input, output: result.output } : { state: "input-available", input: call.input }
4718
5003
  });
4719
5004
  parts.push(...pushedByCall.get(call.id) ?? []);
@@ -4951,6 +5236,12 @@ function useAgentChat(options = {}) {
4951
5236
  return { ...turnBody(), ...regenerate2 ? { regenerate: true } : {} };
4952
5237
  },
4953
5238
  getResumeRunId: () => latest.current.resumeRunId ?? autoResumeRunIdRef.current,
5239
+ // A stream attached to after a reload carries no `meta` frame: without this the chat would
5240
+ // be reading a run it cannot name, and `cancel()` would stop nothing.
5241
+ onResumeAttached: (resumedRunId) => {
5242
+ settlingRunIdRef.current = resumedRunId;
5243
+ setRunId(resumedRunId);
5244
+ },
4954
5245
  onMeta,
4955
5246
  onAttemptStart: () => {
4956
5247
  settlingRunIdRef.current = void 0;
@@ -5420,7 +5711,14 @@ function useAgentChat(options = {}) {
5420
5711
  } catch {
5421
5712
  }
5422
5713
  }
5423
- }, [client]);
5714
+ const threadId = currentThreadId();
5715
+ if (threadId !== void 0 && queueStateRef.current.items.length > 0 && typeof client.getQueue === "function") {
5716
+ try {
5717
+ applyQueue(await client.getQueue(threadId));
5718
+ } catch {
5719
+ }
5720
+ }
5721
+ }, [client, currentThreadId, applyQueue]);
5424
5722
  const approve = useCallback11(
5425
5723
  async ({ toolCallId, remember, via }) => {
5426
5724
  await requireBackendMethod(
@@ -5759,6 +6057,7 @@ export {
5759
6057
  RUN_NOT_ACTIVE_CODE,
5760
6058
  SPEECH_LANGUAGES,
5761
6059
  acceptsFile,
6060
+ agUiChatStream,
5762
6061
  applyCompletion,
5763
6062
  approvalCountdown,
5764
6063
  attachmentFile,
@@ -5791,6 +6090,7 @@ export {
5791
6090
  phraseFor,
5792
6091
  readPath,
5793
6092
  readReasoningMs,
6093
+ reframeAgUiStream,
5794
6094
  requireBackendMethod,
5795
6095
  resolveResultView,
5796
6096
  storedMessageToUiMessage,