@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/README.md CHANGED
@@ -621,6 +621,35 @@ const readCookie = (name: string) =>
621
621
  </AgentProvider>;
622
622
  ```
623
623
 
624
+
625
+ ### An AG-UI agent (`agUiChatStream`)
626
+
627
+ `useAgentChat` can drive any [AG-UI 1.0](https://docs.ag-ui.com/spec/1.0) producer — this library's
628
+ own (`@adonis-agora/agent/ag-ui`), CopilotKit's, a Python or .NET agent. Put `agUiChatStream` behind
629
+ `openChatStream`: it POSTs a `RunAgentInput` (the send's text as the user message, `pageContext`,
630
+ `agent` and `model` in `forwardedProps`) and hands the transport the answer re-framed in this
631
+ library's stream protocol, so the transcript, tool activity and generative UI render unchanged.
632
+
633
+ ```ts
634
+ import { agUiChatStream, type AgentBackend } from '@dudousxd/nestjs-agent-react';
635
+
636
+ const backend: AgentBackend = {
637
+ ...myBackend,
638
+ openChatStream: (request) =>
639
+ agUiChatStream(request, { url: '/agent/ag-ui', headers: { 'x-csrf-token': csrf } }),
640
+ };
641
+ ```
642
+
643
+ - Text, reasoning, steps, tool calls and tool results map one to one; `RUN_ERROR` is the stream's
644
+ error, a cancelled outcome writes `cancelled`.
645
+ - `CUSTOM` events named `agora.*` (generative UI, title, queue, approvals) become the frames they
646
+ stand for; any other `CUSTOM` is ignored, as the protocol requires.
647
+ - An interrupt outcome arrives as a `ui` part, component `AgUiInterrupt`, props `{ interrupts }`:
648
+ answering it is a new run with `resume`, which the app sends through its own backend.
649
+ - A send without a `threadId` gets a new one (AG-UI's thread id is the consumer's), returned as
650
+ the stream's `threadId`, so `onThreadCreated` fires as usual.
651
+ - `reframeAgUiStream(body, { threadId })` is the re-framing alone, for a backend that fetches itself.
652
+
624
653
  ### Typing ahead: the message queue
625
654
 
626
655
  Send while a turn is still answering and the message waits in the thread's queue — server-side, so
@@ -436,4 +436,4 @@ declare class AgentClient implements AgentBackend {
436
436
  private handleResponse;
437
437
  }
438
438
 
439
- export { type AgentRequestError as A, type CancelResult as C, type ErrorAnswer as E, type HttpErrorListener as H, type MessageFeedbackInput as M, type QueuedMessageUpdate as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AgentBackend as a, type AttachmentUploadStrategy as b, AgentBackendUnsupportedError as c, AgentClient as d, type AgentClientOptions as e, type AgentConnection as f, AgentHttpError as g, type ChatStreamRequest as h, type ChatStreamResponse as i, type QueuedSendResult as j, requireBackendMethod as r };
439
+ export { type AgentRequestError as A, type ChatStreamRequest as C, type ErrorAnswer as E, type HttpErrorListener as H, type MessageFeedbackInput as M, type QueuedMessageUpdate as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AgentBackend as a, type ChatStreamResponse as b, type AttachmentUploadStrategy as c, AgentBackendUnsupportedError as d, AgentClient as e, type AgentClientOptions as f, type AgentConnection as g, AgentHttpError as h, type CancelResult as i, type QueuedSendResult as j, requireBackendMethod as r };
@@ -436,4 +436,4 @@ declare class AgentClient implements AgentBackend {
436
436
  private handleResponse;
437
437
  }
438
438
 
439
- export { type AgentRequestError as A, type CancelResult as C, type ErrorAnswer as E, type HttpErrorListener as H, type MessageFeedbackInput as M, type QueuedMessageUpdate as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AgentBackend as a, type AttachmentUploadStrategy as b, AgentBackendUnsupportedError as c, AgentClient as d, type AgentClientOptions as e, type AgentConnection as f, AgentHttpError as g, type ChatStreamRequest as h, type ChatStreamResponse as i, type QueuedSendResult as j, requireBackendMethod as r };
439
+ export { type AgentRequestError as A, type ChatStreamRequest as C, type ErrorAnswer as E, type HttpErrorListener as H, type MessageFeedbackInput as M, type QueuedMessageUpdate as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AgentBackend as a, type ChatStreamResponse as b, type AttachmentUploadStrategy as c, AgentBackendUnsupportedError as d, AgentClient as e, type AgentClientOptions as f, type AgentConnection as g, AgentHttpError as h, type CancelResult as i, type QueuedSendResult as j, requireBackendMethod as r };
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  RUN_NOT_ACTIVE_CODE: () => RUN_NOT_ACTIVE_CODE,
48
48
  SPEECH_LANGUAGES: () => SPEECH_LANGUAGES,
49
49
  acceptsFile: () => acceptsFile,
50
+ agUiChatStream: () => agUiChatStream,
50
51
  applyCompletion: () => applyCompletion,
51
52
  approvalCountdown: () => approvalCountdown,
52
53
  attachmentFile: () => attachmentFile,
@@ -79,6 +80,7 @@ __export(src_exports, {
79
80
  phraseFor: () => phraseFor,
80
81
  readPath: () => readPath,
81
82
  readReasoningMs: () => readReasoningMs,
83
+ reframeAgUiStream: () => reframeAgUiStream,
82
84
  requireBackendMethod: () => requireBackendMethod,
83
85
  resolveResultView: () => resolveResultView,
84
86
  storedMessageToUiMessage: () => storedMessageToUiMessage,
@@ -679,6 +681,7 @@ var AgentChatTransport = class {
679
681
  return null;
680
682
  }
681
683
  this.captureHeaderMeta(response);
684
+ this.options.onResumeAttached?.(response.runId ?? runId);
682
685
  return this.toChunkStream(response, {
683
686
  runId,
684
687
  ...headers !== void 0 ? { headers } : {}
@@ -1924,6 +1927,282 @@ function useAttachments(options = {}) {
1924
1927
  }, [items, add, remove, retry, clear, inputProps, dropZoneProps, dragDepth, onPaste]);
1925
1928
  }
1926
1929
 
1930
+ // src/ag-ui-backend.ts
1931
+ function newId() {
1932
+ return globalThis.crypto.randomUUID();
1933
+ }
1934
+ function isRecord2(value) {
1935
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1936
+ }
1937
+ async function defaultRefusal(response) {
1938
+ try {
1939
+ const body = await response.json();
1940
+ if (typeof body.message === "string" && body.message.length > 0) return new Error(body.message);
1941
+ } catch {
1942
+ }
1943
+ return new Error(`The agent refused the request (${response.status}).`);
1944
+ }
1945
+ function defaultForwardedProps(body) {
1946
+ const out = {};
1947
+ for (const key of ["pageContext", "agent", "model"]) {
1948
+ if (body[key] !== void 0 && body[key] !== null) out[key] = body[key];
1949
+ }
1950
+ return Object.keys(out).length > 0 ? out : void 0;
1951
+ }
1952
+ async function agUiChatStream(request, options) {
1953
+ const body = request.body;
1954
+ const threadId = typeof body.threadId === "string" ? body.threadId : newId();
1955
+ const message = typeof body.message === "string" ? body.message : "";
1956
+ const forwarded = (options.forwardedProps ?? defaultForwardedProps)(body);
1957
+ const input = {
1958
+ threadId,
1959
+ runId: newId(),
1960
+ protocolVersion: "1.0",
1961
+ messages: [{ id: newId(), role: "user", content: message }],
1962
+ ...forwarded !== void 0 ? { forwardedProps: forwarded } : {}
1963
+ };
1964
+ const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
1965
+ const response = await doFetch(options.url, {
1966
+ method: "POST",
1967
+ credentials: "same-origin",
1968
+ headers: {
1969
+ "content-type": "application/json",
1970
+ accept: "text/event-stream",
1971
+ ...options.headers,
1972
+ ...request.headers
1973
+ },
1974
+ body: JSON.stringify(input),
1975
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
1976
+ });
1977
+ if (!response.ok || response.body === null) {
1978
+ throw await (options.refusal ?? defaultRefusal)(response);
1979
+ }
1980
+ const runId = response.headers.get("x-agent-run-id") ?? void 0;
1981
+ return {
1982
+ body: reframeAgUiStream(response.body, { threadId }),
1983
+ threadId,
1984
+ ...runId !== void 0 ? { runId } : {}
1985
+ };
1986
+ }
1987
+ function reframeAgUiStream(source, ids) {
1988
+ const encoder = new TextEncoder();
1989
+ const decoder = new TextDecoder();
1990
+ const reframer = createReframer(ids.threadId);
1991
+ const reader = source.getReader();
1992
+ let buffer = "";
1993
+ return new ReadableStream({
1994
+ async pull(controller) {
1995
+ for (; ; ) {
1996
+ const { value, done } = await reader.read();
1997
+ if (done) {
1998
+ controller.close();
1999
+ return;
2000
+ }
2001
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, "\n");
2002
+ let out = "";
2003
+ let separator = buffer.indexOf("\n\n");
2004
+ while (separator !== -1) {
2005
+ const block = buffer.slice(0, separator);
2006
+ buffer = buffer.slice(separator + 2);
2007
+ separator = buffer.indexOf("\n\n");
2008
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
2009
+ if (data.length === 0) continue;
2010
+ let event;
2011
+ try {
2012
+ event = JSON.parse(data);
2013
+ } catch {
2014
+ continue;
2015
+ }
2016
+ if (isRecord2(event) && typeof event.type === "string") {
2017
+ out += reframer.push(event);
2018
+ }
2019
+ if (reframer.closed) break;
2020
+ }
2021
+ if (out.length > 0) controller.enqueue(encoder.encode(out));
2022
+ if (reframer.closed) {
2023
+ void reader.cancel().catch(() => void 0);
2024
+ controller.close();
2025
+ return;
2026
+ }
2027
+ if (out.length > 0) return;
2028
+ }
2029
+ },
2030
+ cancel(reason) {
2031
+ return reader.cancel(reason).catch(() => void 0);
2032
+ }
2033
+ });
2034
+ }
2035
+ function createReframer(threadId) {
2036
+ let seq = 0;
2037
+ let metaSent = false;
2038
+ const tools = /* @__PURE__ */ new Map();
2039
+ const state = {
2040
+ closed: false,
2041
+ push(event) {
2042
+ if (state.closed) return "";
2043
+ let out = "";
2044
+ const frame = (streamEvent) => {
2045
+ seq += 1;
2046
+ out += `id: ${seq}
2047
+ data: ${JSON.stringify(streamEvent)}
2048
+
2049
+ `;
2050
+ };
2051
+ const meta = (runId, thread) => {
2052
+ if (metaSent) return;
2053
+ metaSent = true;
2054
+ out += `event: meta
2055
+ data: ${JSON.stringify({
2056
+ runId: typeof runId === "string" ? runId : "",
2057
+ threadId: typeof thread === "string" ? thread : threadId
2058
+ })}
2059
+
2060
+ `;
2061
+ };
2062
+ switch (event.type) {
2063
+ case "RUN_STARTED":
2064
+ pendingStart = { runId: event.runId, threadId: event.threadId };
2065
+ return out;
2066
+ case "CUSTOM": {
2067
+ const value = event.value;
2068
+ if (event.name === "agora.run" && isRecord2(value)) {
2069
+ meta(value.runId, pendingStart?.threadId ?? value.threadId);
2070
+ pendingStart = void 0;
2071
+ return out;
2072
+ }
2073
+ flushStart();
2074
+ if (!isRecord2(value)) return out;
2075
+ if (event.name === "agora.ui") frame({ kind: "ui", ...value });
2076
+ else if (event.name === "agora.title" && typeof value.title === "string") {
2077
+ frame({ kind: "title", title: value.title });
2078
+ } else if (event.name === "agora.queue") frame({ kind: "queue", ...value });
2079
+ else if (event.name === "agora.approval-requested") {
2080
+ frame({ ...value, kind: "approval-requested" });
2081
+ } else if (event.name === "agora.approval-settled") {
2082
+ frame({ ...value, kind: "approval-settled" });
2083
+ }
2084
+ return out;
2085
+ }
2086
+ default:
2087
+ flushStart();
2088
+ }
2089
+ switch (event.type) {
2090
+ case "TEXT_MESSAGE_CONTENT":
2091
+ case "TEXT_MESSAGE_CHUNK":
2092
+ if (typeof event.delta === "string" && event.delta.length > 0) {
2093
+ frame({ kind: "text", text: event.delta });
2094
+ }
2095
+ break;
2096
+ case "REASONING_MESSAGE_CONTENT":
2097
+ case "REASONING_MESSAGE_CHUNK":
2098
+ if (typeof event.delta === "string" && event.delta.length > 0) {
2099
+ frame({ kind: "reasoning", text: event.delta });
2100
+ }
2101
+ break;
2102
+ case "STEP_STARTED":
2103
+ frame({ kind: "step-start" });
2104
+ break;
2105
+ case "STEP_FINISHED":
2106
+ frame({ kind: "step-finish" });
2107
+ break;
2108
+ case "TOOL_CALL_START":
2109
+ case "TOOL_CALL_CHUNK": {
2110
+ const id = String(event.toolCallId ?? "");
2111
+ if (id === "") break;
2112
+ if (!tools.has(id)) {
2113
+ const name = typeof event.toolCallName === "string" ? event.toolCallName : "";
2114
+ tools.set(id, { name, args: "" });
2115
+ frame({ kind: "tool-input-start", id, name, toolKind: "read" });
2116
+ }
2117
+ if (event.type === "TOOL_CALL_CHUNK" && typeof event.delta === "string") {
2118
+ tools.get(id).args += event.delta;
2119
+ frame({ kind: "tool-input-delta", id, delta: event.delta });
2120
+ }
2121
+ break;
2122
+ }
2123
+ case "TOOL_CALL_ARGS": {
2124
+ const call = tools.get(String(event.toolCallId));
2125
+ if (call === void 0 || typeof event.delta !== "string") break;
2126
+ call.args += event.delta;
2127
+ frame({ kind: "tool-input-delta", id: String(event.toolCallId), delta: event.delta });
2128
+ break;
2129
+ }
2130
+ case "TOOL_CALL_END": {
2131
+ const id = String(event.toolCallId);
2132
+ const call = tools.get(id);
2133
+ if (call === void 0) break;
2134
+ frame({
2135
+ kind: "tool-input-available",
2136
+ id,
2137
+ name: call.name,
2138
+ input: parseJson(call.args, {}),
2139
+ toolKind: "read"
2140
+ });
2141
+ break;
2142
+ }
2143
+ case "TOOL_CALL_RESULT": {
2144
+ const id = String(event.toolCallId);
2145
+ const content = contentText(event.content);
2146
+ const outcome = isRecord2(event.metadata) ? event.metadata["agora.outcome"] : void 0;
2147
+ if (outcome === "error") frame({ kind: "tool-output-error", id, error: content });
2148
+ else if (outcome === "denied") frame({ kind: "tool-output-denied", id, reason: content });
2149
+ else frame({ kind: "tool-output", id, output: parseJson(content, content) });
2150
+ break;
2151
+ }
2152
+ case "RUN_ERROR":
2153
+ state.closed = true;
2154
+ out += `event: error
2155
+ data: ${JSON.stringify({
2156
+ code: typeof event.code === "string" ? event.code : "run_failed",
2157
+ message: typeof event.message === "string" ? event.message : "The run failed."
2158
+ })}
2159
+
2160
+ `;
2161
+ break;
2162
+ case "RUN_FINISHED": {
2163
+ const outcome = isRecord2(event.outcome) ? event.outcome : void 0;
2164
+ if (outcome?.type === "cancelled") frame({ kind: "cancelled" });
2165
+ if (outcome?.type === "interrupt" && Array.isArray(outcome.interrupts)) {
2166
+ frame({
2167
+ kind: "ui",
2168
+ id: `ag-ui:interrupt:${String(event.runId ?? "")}`,
2169
+ component: "AgUiInterrupt",
2170
+ props: { interrupts: outcome.interrupts }
2171
+ });
2172
+ }
2173
+ state.closed = true;
2174
+ out += "event: done\ndata: {}\n\n";
2175
+ break;
2176
+ }
2177
+ default:
2178
+ break;
2179
+ }
2180
+ return out;
2181
+ function flushStart() {
2182
+ if (pendingStart !== void 0) {
2183
+ meta(pendingStart.runId, pendingStart.threadId);
2184
+ pendingStart = void 0;
2185
+ }
2186
+ }
2187
+ }
2188
+ };
2189
+ let pendingStart;
2190
+ return state;
2191
+ }
2192
+ function parseJson(text, fallback2) {
2193
+ if (text.trim().length === 0) return fallback2;
2194
+ try {
2195
+ return JSON.parse(text);
2196
+ } catch {
2197
+ return fallback2;
2198
+ }
2199
+ }
2200
+ function contentText(content) {
2201
+ if (typeof content === "string") return content;
2202
+ if (!Array.isArray(content)) return "";
2203
+ return content.filter((part) => isRecord2(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
2204
+ }
2205
+
1927
2206
  // src/catalog/use-agents.ts
1928
2207
  var import_react9 = require("react");
1929
2208
 
@@ -2561,7 +2840,7 @@ function phraseFor(presentation, input, isSettled2, fallback2 = { running: "Work
2561
2840
  }
2562
2841
 
2563
2842
  // src/presentation/result-view.ts
2564
- function isRecord2(value) {
2843
+ function isRecord3(value) {
2565
2844
  return typeof value === "object" && value !== null && !Array.isArray(value);
2566
2845
  }
2567
2846
  function toText(value) {
@@ -2573,18 +2852,18 @@ function toText(value) {
2573
2852
  return "\u2014";
2574
2853
  }
2575
2854
  function inferResultView(output) {
2576
- if (!isRecord2(output)) return void 0;
2855
+ if (!isRecord3(output)) return void 0;
2577
2856
  for (const [key, value] of Object.entries(output)) {
2578
2857
  if (!Array.isArray(value) || value.length === 0) continue;
2579
2858
  if (value.every((entry) => typeof entry === "string")) {
2580
2859
  return { kind: "log", lines: key };
2581
2860
  }
2582
- if (value.every(isRecord2)) {
2861
+ if (value.every(isRecord3)) {
2583
2862
  const first = value[0];
2584
2863
  return {
2585
2864
  kind: "table",
2586
2865
  rows: key,
2587
- columns: Object.keys(first).filter((column) => !isRecord2(first[column])).map((column) => ({ path: column, label: column }))
2866
+ columns: Object.keys(first).filter((column) => !isRecord3(first[column])).map((column) => ({ path: column, label: column }))
2588
2867
  };
2589
2868
  }
2590
2869
  }
@@ -2911,7 +3190,7 @@ function isSettled(state) {
2911
3190
  }
2912
3191
  function toolKind(part) {
2913
3192
  const metadata = part.toolMetadata;
2914
- return isRecord3(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
3193
+ return isRecord4(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
2915
3194
  }
2916
3195
  function isAwaitingApproval(part) {
2917
3196
  if (part.state === "approval-requested") {
@@ -2924,7 +3203,7 @@ function isAwaitingApproval(part) {
2924
3203
  }
2925
3204
  function toolParentId(part) {
2926
3205
  const metadata = part.toolMetadata;
2927
- return isRecord3(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
3206
+ return isRecord4(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
2928
3207
  }
2929
3208
  function nestToolCalls(calls) {
2930
3209
  const byId = new Map(calls.map((call) => [call.toolCallId, call]));
@@ -2983,12 +3262,12 @@ function buildToolCall(part, options, approval, catalog) {
2983
3262
  }
2984
3263
  function readElicitationRequest(part) {
2985
3264
  const input = part.input;
2986
- if (!isRecord3(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
3265
+ if (!isRecord4(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
2987
3266
  return null;
2988
3267
  }
2989
3268
  const questions = [];
2990
3269
  for (const candidate of input.questions) {
2991
- if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
3270
+ if (!isRecord4(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
2992
3271
  return null;
2993
3272
  }
2994
3273
  const input2 = (0, import_nestjs_agent_core3.readElicitationInput)(candidate.input) ?? null;
@@ -2998,7 +3277,7 @@ function readElicitationRequest(part) {
2998
3277
  }
2999
3278
  const options = [];
3000
3279
  for (const option of rawOptions) {
3001
- if (!isRecord3(option) || typeof option.value !== "string" || typeof option.label !== "string") {
3280
+ if (!isRecord4(option) || typeof option.value !== "string" || typeof option.label !== "string") {
3002
3281
  return null;
3003
3282
  }
3004
3283
  options.push({
@@ -3025,7 +3304,7 @@ function readElicitationRequest(part) {
3025
3304
  }
3026
3305
  function readElicitationOutcome(part) {
3027
3306
  const output = part.output;
3028
- if (!isRecord3(output) || !isRecord3(output.answers) || typeof output.skipped !== "boolean") {
3307
+ if (!isRecord4(output) || !isRecord4(output.answers) || typeof output.skipped !== "boolean") {
3029
3308
  return null;
3030
3309
  }
3031
3310
  const answers = {};
@@ -3150,7 +3429,7 @@ function readApprovals(parts) {
3150
3429
  continue;
3151
3430
  }
3152
3431
  const data = part.data;
3153
- if (!isRecord3(data) || typeof data.id !== "string") {
3432
+ if (!isRecord4(data) || typeof data.id !== "string") {
3154
3433
  continue;
3155
3434
  }
3156
3435
  const approval = entry(data.id);
@@ -3203,7 +3482,7 @@ function statusFromPart(call) {
3203
3482
  }
3204
3483
  function readUiComponent(part) {
3205
3484
  const data = part.data;
3206
- if (!isRecord3(data) || typeof data.component !== "string") {
3485
+ if (!isRecord4(data) || typeof data.component !== "string") {
3207
3486
  return null;
3208
3487
  }
3209
3488
  const id = typeof data.id === "string" ? data.id : part.id;
@@ -3213,29 +3492,29 @@ function readUiComponent(part) {
3213
3492
  return {
3214
3493
  id,
3215
3494
  component: data.component,
3216
- props: isRecord3(data.props) ? data.props : {},
3495
+ props: isRecord4(data.props) ? data.props : {},
3217
3496
  version: typeof data.version === "number" ? data.version : null,
3218
3497
  toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : null
3219
3498
  };
3220
3499
  }
3221
- function isRecord3(value) {
3500
+ function isRecord4(value) {
3222
3501
  return typeof value === "object" && value !== null;
3223
3502
  }
3224
3503
  function readQuery(part) {
3225
3504
  const input = part.input;
3226
- if (!isRecord3(input) || typeof input.query !== "string") {
3505
+ if (!isRecord4(input) || typeof input.query !== "string") {
3227
3506
  return null;
3228
3507
  }
3229
3508
  return input.query;
3230
3509
  }
3231
3510
  function readPassages(part) {
3232
3511
  const output = part.output;
3233
- if (!isRecord3(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
3512
+ if (!isRecord4(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
3234
3513
  return null;
3235
3514
  }
3236
3515
  const passages = [];
3237
3516
  for (const candidate of output.passages) {
3238
- if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
3517
+ if (!isRecord4(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
3239
3518
  return null;
3240
3519
  }
3241
3520
  passages.push({
@@ -3243,7 +3522,7 @@ function readPassages(part) {
3243
3522
  text: candidate.text,
3244
3523
  score: typeof candidate.score === "number" ? candidate.score : 0,
3245
3524
  ...typeof candidate.source === "string" ? { source: candidate.source } : {},
3246
- ...isRecord3(candidate.metadata) ? { metadata: candidate.metadata } : {}
3525
+ ...isRecord4(candidate.metadata) ? { metadata: candidate.metadata } : {}
3247
3526
  });
3248
3527
  }
3249
3528
  return passages;
@@ -4803,6 +5082,14 @@ function storedMessageToUiMessage(message) {
4803
5082
  approved: false,
4804
5083
  ...refusalReason(result) !== void 0 ? { reason: refusalReason(result) } : {}
4805
5084
  }
5085
+ } : typeof result.error === "string" && result.error !== "" ? {
5086
+ // A call that FAILED — the tool threw, its input was refused, or its turn died
5087
+ // before it was settled — is not an available output either. The live stream says
5088
+ // so with `tool-output-error`; a reloaded thread has to say the same, or the card of
5089
+ // an action that never ran comes back reading as one that completed.
5090
+ state: "output-error",
5091
+ input: call.input,
5092
+ errorText: result.error
4806
5093
  } : { state: "output-available", input: call.input, output: result.output } : { state: "input-available", input: call.input }
4807
5094
  });
4808
5095
  parts.push(...pushedByCall.get(call.id) ?? []);
@@ -5040,6 +5327,12 @@ function useAgentChat(options = {}) {
5040
5327
  return { ...turnBody(), ...regenerate2 ? { regenerate: true } : {} };
5041
5328
  },
5042
5329
  getResumeRunId: () => latest.current.resumeRunId ?? autoResumeRunIdRef.current,
5330
+ // A stream attached to after a reload carries no `meta` frame: without this the chat would
5331
+ // be reading a run it cannot name, and `cancel()` would stop nothing.
5332
+ onResumeAttached: (resumedRunId) => {
5333
+ settlingRunIdRef.current = resumedRunId;
5334
+ setRunId(resumedRunId);
5335
+ },
5043
5336
  onMeta,
5044
5337
  onAttemptStart: () => {
5045
5338
  settlingRunIdRef.current = void 0;
@@ -5509,7 +5802,14 @@ function useAgentChat(options = {}) {
5509
5802
  } catch {
5510
5803
  }
5511
5804
  }
5512
- }, [client]);
5805
+ const threadId = currentThreadId();
5806
+ if (threadId !== void 0 && queueStateRef.current.items.length > 0 && typeof client.getQueue === "function") {
5807
+ try {
5808
+ applyQueue(await client.getQueue(threadId));
5809
+ } catch {
5810
+ }
5811
+ }
5812
+ }, [client, currentThreadId, applyQueue]);
5513
5813
  const approve = (0, import_react23.useCallback)(
5514
5814
  async ({ toolCallId, remember, via }) => {
5515
5815
  await requireBackendMethod(
@@ -5849,6 +6149,7 @@ function isRunNotActiveError(error) {
5849
6149
  RUN_NOT_ACTIVE_CODE,
5850
6150
  SPEECH_LANGUAGES,
5851
6151
  acceptsFile,
6152
+ agUiChatStream,
5852
6153
  applyCompletion,
5853
6154
  approvalCountdown,
5854
6155
  attachmentFile,
@@ -5881,6 +6182,7 @@ function isRunNotActiveError(error) {
5881
6182
  phraseFor,
5882
6183
  readPath,
5883
6184
  readReasoningMs,
6185
+ reframeAgUiStream,
5884
6186
  requireBackendMethod,
5885
6187
  resolveResultView,
5886
6188
  storedMessageToUiMessage,