@dudousxd/nestjs-agent-react 0.34.1 → 0.34.2

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.d.cts CHANGED
@@ -390,18 +390,33 @@ declare function useAttachments(options?: UseAttachmentsOptions): AttachmentsSta
390
390
  * What maps where (the inverse of the producer in `@adonis-agora/agent/ag-ui`):
391
391
  * - `TEXT_MESSAGE_*` → `text`; `REASONING_MESSAGE_*` → `reasoning`; `STEP_*` → `step-start` /
392
392
  * `step-finish`;
393
- * - `TOOL_CALL_START` / `ARGS` / `END` → `tool-input-start` / `-delta` / `-available`;
394
- * `TOOL_CALL_RESULT` → `tool-output` (or `tool-output-error` / `-denied`, from
395
- * `metadata['agora.outcome']`);
393
+ * - `TOOL_CALL_START` / `ARGS` / `END` → `tool-input-start` / `-delta` / `-available`, with the
394
+ * `toolKind` and `parentId` from `metadata['agora.toolKind']` / `['agora.parentId']` (a call
395
+ * without them is a `read`); `TOOL_CALL_RESULT` → `tool-output` (or `tool-output-error` /
396
+ * `-denied`, from `metadata['agora.outcome']`);
396
397
  * - `ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA` → a `ui` part named `AgUiActivity`, id
397
398
  * `activity:<messageId>`, props `{ activityType, content }` — the whole content each time (the
398
399
  * delta's JSON Patch applied here), so a repeat replaces the widget in place;
399
400
  * - `RUN_ERROR` → the stream's `event: error`; `RUN_FINISHED` → `event: done` (a cancelled
400
- * outcome writes `cancelled` first; an interrupt outcome writes a `ui` part named
401
- * `AgUiInterrupt` carrying the interrupts, for the app to render);
401
+ * outcome writes `cancelled` first). An interrupt outcome whose every interrupt this stream
402
+ * already showed (`agora.approval-requested` / `agora.elicitation`) ends the stream WITHOUT
403
+ * `done`: the run is parked, not over, so the transport re-attaches to it on the native route
404
+ * (`chat/:runId/stream?after=`) and the rest of the run — after the person decides through the
405
+ * native `tool-call` routes — streams into the same message. An interrupt nothing showed (another
406
+ * producer's) becomes a `ui` part named `AgUiInterrupt` carrying it, for the app to render;
407
+ * - the producer's SSE `id:` (the run's own sequence number, which this library's producer writes)
408
+ * is passed on as the cursor the transport re-attaches with; frames of a producer that numbers
409
+ * nothing carry no id, and the transport then does not try to re-attach;
402
410
  * - `CUSTOM` events a producer of this family writes (`agora.ui`, `agora.title`, `agora.queue`,
403
- * `agora.approval-requested` / `-settled`, `agora.run`) → the frames they stand for. Any other
404
- * `CUSTOM` is ignored, as the protocol requires of a consumer that does not know it.
411
+ * `agora.approval-requested` / `-settled`, `agora.elicitation`, `agora.run`) → the frames they
412
+ * stand for; `agora.step-usage` (right after a `STEP_FINISHED`) → that step's `usage`, `costUsd`
413
+ * and `reasoningMs` on its `step-finish`; `agora.action-proposal-decision` → the
414
+ * `data-proposal-decision` part a native decision answers with (`{ threadId, proposalDecision,
415
+ * text }`). Any other `CUSTOM` is ignored, as the protocol requires of a consumer that does not
416
+ * know it.
417
+ *
418
+ * And on the way in: the send's staged `attachments` (`{ mediaId }`) become `file` content parts
419
+ * the producer resolves for the caller, and `regenerate: true` rides `forwardedProps`.
405
420
  */
406
421
  interface AgUiChatStreamOptions {
407
422
  /** The producer's endpoint (`POST`, `RunAgentInput` in, `text/event-stream` out). */
@@ -411,16 +426,20 @@ interface AgUiChatStreamOptions {
411
426
  /** Defaults to the global `fetch`, with `credentials: 'same-origin'`. */
412
427
  fetch?: typeof fetch;
413
428
  /**
414
- * Shape `forwardedProps` from the send's body. Default: `{ pageContext, agent, model, persona, uiCapabilities }` from the
415
- * body, whichever are present.
429
+ * Shape `forwardedProps` from the send's body. Default: `{ pageContext, agent, model, persona,
430
+ * uiCapabilities, regenerate }` from the body, whichever are present.
416
431
  */
417
432
  forwardedProps?: (body: Record<string, unknown>) => unknown;
418
433
  /** Read the refusal of a non-2xx answer. Default: the JSON body's `message`, else the status. */
419
434
  refusal?: (response: Response) => Promise<Error>;
420
435
  /**
421
- * The user message's `content` for the send. Default: the body's `message` as a string. Return
422
- * a list of AG-UI content parts to send a file with it — `{ type: 'image' | 'document' | …,
423
- * source: { type: 'data', value: <base64>, mimeType } }` — and the text as a `text` part.
436
+ * The user message's `content` for the send. Default: the body's `message` as a string — or,
437
+ * when the send carries staged `attachments` (`{ mediaId, contentType? }`, what the composer
438
+ * uploads), a `text` part followed by one `file` part per attachment
439
+ * (`source: { type: 'file', value: <mediaId>, provider: 'nestjs-agent' }`), which this library's
440
+ * producer resolves for the caller. Return your own list of AG-UI content parts to send inline
441
+ * bytes instead — `{ type: 'image' | 'document' | …, source: { type: 'data', value: <base64>,
442
+ * mimeType } }`.
424
443
  */
425
444
  content?: (body: Record<string, unknown>) => string | AgUiContentPart[];
426
445
  }
@@ -450,8 +469,8 @@ type AgUiContentPart = {
450
469
  declare function agUiChatStream(request: ChatStreamRequest, options: AgUiChatStreamOptions): Promise<ChatStreamResponse>;
451
470
  /**
452
471
  * Re-frame an AG-UI event stream (SSE, one event per `data:`) in this library's stream protocol:
453
- * `event: meta`, numbered `data:` frames of {@link AgentStreamEvent}, then `event: done` or
454
- * `event: error`. Exported for a backend that fetches the stream itself.
472
+ * `event: meta`, `data:` frames of {@link AgentStreamEvent} (numbered with the run's own sequence
473
+ * when the producer numbers its events), then `event: done` or `event: error`. Exported for a backend that fetches the stream itself.
455
474
  */
456
475
  declare function reframeAgUiStream(source: ReadableStream<Uint8Array>, ids: {
457
476
  threadId: string;
package/dist/index.d.ts CHANGED
@@ -390,18 +390,33 @@ declare function useAttachments(options?: UseAttachmentsOptions): AttachmentsSta
390
390
  * What maps where (the inverse of the producer in `@adonis-agora/agent/ag-ui`):
391
391
  * - `TEXT_MESSAGE_*` → `text`; `REASONING_MESSAGE_*` → `reasoning`; `STEP_*` → `step-start` /
392
392
  * `step-finish`;
393
- * - `TOOL_CALL_START` / `ARGS` / `END` → `tool-input-start` / `-delta` / `-available`;
394
- * `TOOL_CALL_RESULT` → `tool-output` (or `tool-output-error` / `-denied`, from
395
- * `metadata['agora.outcome']`);
393
+ * - `TOOL_CALL_START` / `ARGS` / `END` → `tool-input-start` / `-delta` / `-available`, with the
394
+ * `toolKind` and `parentId` from `metadata['agora.toolKind']` / `['agora.parentId']` (a call
395
+ * without them is a `read`); `TOOL_CALL_RESULT` → `tool-output` (or `tool-output-error` /
396
+ * `-denied`, from `metadata['agora.outcome']`);
396
397
  * - `ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA` → a `ui` part named `AgUiActivity`, id
397
398
  * `activity:<messageId>`, props `{ activityType, content }` — the whole content each time (the
398
399
  * delta's JSON Patch applied here), so a repeat replaces the widget in place;
399
400
  * - `RUN_ERROR` → the stream's `event: error`; `RUN_FINISHED` → `event: done` (a cancelled
400
- * outcome writes `cancelled` first; an interrupt outcome writes a `ui` part named
401
- * `AgUiInterrupt` carrying the interrupts, for the app to render);
401
+ * outcome writes `cancelled` first). An interrupt outcome whose every interrupt this stream
402
+ * already showed (`agora.approval-requested` / `agora.elicitation`) ends the stream WITHOUT
403
+ * `done`: the run is parked, not over, so the transport re-attaches to it on the native route
404
+ * (`chat/:runId/stream?after=`) and the rest of the run — after the person decides through the
405
+ * native `tool-call` routes — streams into the same message. An interrupt nothing showed (another
406
+ * producer's) becomes a `ui` part named `AgUiInterrupt` carrying it, for the app to render;
407
+ * - the producer's SSE `id:` (the run's own sequence number, which this library's producer writes)
408
+ * is passed on as the cursor the transport re-attaches with; frames of a producer that numbers
409
+ * nothing carry no id, and the transport then does not try to re-attach;
402
410
  * - `CUSTOM` events a producer of this family writes (`agora.ui`, `agora.title`, `agora.queue`,
403
- * `agora.approval-requested` / `-settled`, `agora.run`) → the frames they stand for. Any other
404
- * `CUSTOM` is ignored, as the protocol requires of a consumer that does not know it.
411
+ * `agora.approval-requested` / `-settled`, `agora.elicitation`, `agora.run`) → the frames they
412
+ * stand for; `agora.step-usage` (right after a `STEP_FINISHED`) → that step's `usage`, `costUsd`
413
+ * and `reasoningMs` on its `step-finish`; `agora.action-proposal-decision` → the
414
+ * `data-proposal-decision` part a native decision answers with (`{ threadId, proposalDecision,
415
+ * text }`). Any other `CUSTOM` is ignored, as the protocol requires of a consumer that does not
416
+ * know it.
417
+ *
418
+ * And on the way in: the send's staged `attachments` (`{ mediaId }`) become `file` content parts
419
+ * the producer resolves for the caller, and `regenerate: true` rides `forwardedProps`.
405
420
  */
406
421
  interface AgUiChatStreamOptions {
407
422
  /** The producer's endpoint (`POST`, `RunAgentInput` in, `text/event-stream` out). */
@@ -411,16 +426,20 @@ interface AgUiChatStreamOptions {
411
426
  /** Defaults to the global `fetch`, with `credentials: 'same-origin'`. */
412
427
  fetch?: typeof fetch;
413
428
  /**
414
- * Shape `forwardedProps` from the send's body. Default: `{ pageContext, agent, model, persona, uiCapabilities }` from the
415
- * body, whichever are present.
429
+ * Shape `forwardedProps` from the send's body. Default: `{ pageContext, agent, model, persona,
430
+ * uiCapabilities, regenerate }` from the body, whichever are present.
416
431
  */
417
432
  forwardedProps?: (body: Record<string, unknown>) => unknown;
418
433
  /** Read the refusal of a non-2xx answer. Default: the JSON body's `message`, else the status. */
419
434
  refusal?: (response: Response) => Promise<Error>;
420
435
  /**
421
- * The user message's `content` for the send. Default: the body's `message` as a string. Return
422
- * a list of AG-UI content parts to send a file with it — `{ type: 'image' | 'document' | …,
423
- * source: { type: 'data', value: <base64>, mimeType } }` — and the text as a `text` part.
436
+ * The user message's `content` for the send. Default: the body's `message` as a string — or,
437
+ * when the send carries staged `attachments` (`{ mediaId, contentType? }`, what the composer
438
+ * uploads), a `text` part followed by one `file` part per attachment
439
+ * (`source: { type: 'file', value: <mediaId>, provider: 'nestjs-agent' }`), which this library's
440
+ * producer resolves for the caller. Return your own list of AG-UI content parts to send inline
441
+ * bytes instead — `{ type: 'image' | 'document' | …, source: { type: 'data', value: <base64>,
442
+ * mimeType } }`.
424
443
  */
425
444
  content?: (body: Record<string, unknown>) => string | AgUiContentPart[];
426
445
  }
@@ -450,8 +469,8 @@ type AgUiContentPart = {
450
469
  declare function agUiChatStream(request: ChatStreamRequest, options: AgUiChatStreamOptions): Promise<ChatStreamResponse>;
451
470
  /**
452
471
  * Re-frame an AG-UI event stream (SSE, one event per `data:`) in this library's stream protocol:
453
- * `event: meta`, numbered `data:` frames of {@link AgentStreamEvent}, then `event: done` or
454
- * `event: error`. Exported for a backend that fetches the stream itself.
472
+ * `event: meta`, `data:` frames of {@link AgentStreamEvent} (numbered with the run's own sequence
473
+ * when the producer numbers its events), then `event: done` or `event: error`. Exported for a backend that fetches the stream itself.
455
474
  */
456
475
  declare function reframeAgUiStream(source: ReadableStream<Uint8Array>, ids: {
457
476
  threadId: string;
package/dist/index.js CHANGED
@@ -948,7 +948,10 @@ var AgentChatTransport = class {
948
948
  forwardAsData(event, true);
949
949
  break;
950
950
  default:
951
- forwardAsData(event, false);
951
+ forwardAsData(
952
+ event,
953
+ event.kind === "proposal-decision"
954
+ );
952
955
  break;
953
956
  }
954
957
  }
@@ -1969,8 +1972,33 @@ function defaultForwardedProps(body) {
1969
1972
  for (const key of ["pageContext", "agent", "model", "persona", "uiCapabilities"]) {
1970
1973
  if (body[key] !== void 0 && body[key] !== null) out[key] = body[key];
1971
1974
  }
1975
+ if (body.regenerate === true) out.regenerate = true;
1972
1976
  return Object.keys(out).length > 0 ? out : void 0;
1973
1977
  }
1978
+ var MEDIA_PROVIDER = "nestjs-agent";
1979
+ function partTypeFor(contentType) {
1980
+ const major = contentType?.split("/")[0]?.toLowerCase();
1981
+ return major === "image" || major === "audio" || major === "video" ? major : "document";
1982
+ }
1983
+ function defaultContent(body, message) {
1984
+ const attachments = Array.isArray(body.attachments) ? body.attachments : [];
1985
+ const files = [];
1986
+ for (const attachment of attachments) {
1987
+ if (!isRecord2(attachment) || typeof attachment.mediaId !== "string") continue;
1988
+ const contentType = typeof attachment.contentType === "string" ? attachment.contentType : void 0;
1989
+ files.push({
1990
+ type: partTypeFor(contentType),
1991
+ source: {
1992
+ type: "file",
1993
+ value: attachment.mediaId,
1994
+ provider: MEDIA_PROVIDER,
1995
+ ...contentType !== void 0 ? { mimeType: contentType } : {}
1996
+ }
1997
+ });
1998
+ }
1999
+ if (files.length === 0) return message;
2000
+ return [...message.length > 0 ? [{ type: "text", text: message }] : [], ...files];
2001
+ }
1974
2002
  async function agUiChatStream(request, options) {
1975
2003
  const body = request.body;
1976
2004
  const threadId = typeof body.threadId === "string" ? body.threadId : newId();
@@ -1980,7 +2008,13 @@ async function agUiChatStream(request, options) {
1980
2008
  threadId,
1981
2009
  runId: newId(),
1982
2010
  protocolVersion: "1.0",
1983
- messages: [{ id: newId(), role: "user", content: options.content?.(body) ?? message }],
2011
+ messages: [
2012
+ {
2013
+ id: newId(),
2014
+ role: "user",
2015
+ content: options.content?.(body) ?? defaultContent(body, message)
2016
+ }
2017
+ ],
1984
2018
  ...forwarded !== void 0 ? { forwardedProps: forwarded } : {}
1985
2019
  };
1986
2020
  const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
@@ -2017,6 +2051,8 @@ function reframeAgUiStream(source, ids) {
2017
2051
  for (; ; ) {
2018
2052
  const { value, done } = await reader.read();
2019
2053
  if (done) {
2054
+ const rest = reframer.flush();
2055
+ if (rest.length > 0) controller.enqueue(encoder.encode(rest));
2020
2056
  controller.close();
2021
2057
  return;
2022
2058
  }
@@ -2027,7 +2063,10 @@ function reframeAgUiStream(source, ids) {
2027
2063
  const block = buffer.slice(0, separator);
2028
2064
  buffer = buffer.slice(separator + 2);
2029
2065
  separator = buffer.indexOf("\n\n");
2030
- const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
2066
+ const lines = block.split("\n");
2067
+ const idLine = lines.find((line) => line.startsWith("id:"));
2068
+ const id = idLine === void 0 ? void 0 : Number(idLine.slice(3).trim());
2069
+ const data = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
2031
2070
  if (data.length === 0) continue;
2032
2071
  let event;
2033
2072
  try {
@@ -2036,7 +2075,10 @@ function reframeAgUiStream(source, ids) {
2036
2075
  continue;
2037
2076
  }
2038
2077
  if (isRecord2(event) && typeof event.type === "string") {
2039
- out += reframer.push(event);
2078
+ out += reframer.push(
2079
+ event,
2080
+ id !== void 0 && Number.isSafeInteger(id) && id > 0 ? id : void 0
2081
+ );
2040
2082
  }
2041
2083
  if (reframer.closed) break;
2042
2084
  }
@@ -2056,21 +2098,55 @@ function reframeAgUiStream(source, ids) {
2056
2098
  }
2057
2099
  function createReframer(threadId) {
2058
2100
  const activities = /* @__PURE__ */ new Map();
2059
- let seq = 0;
2060
2101
  let metaSent = false;
2061
2102
  const tools = /* @__PURE__ */ new Map();
2103
+ let pendingStep;
2104
+ const encodeFrame = (streamEvent) => `data: ${JSON.stringify(streamEvent)}
2105
+
2106
+ `;
2107
+ const flushStep = () => {
2108
+ if (pendingStep === void 0) return "";
2109
+ const step = pendingStep;
2110
+ pendingStep = void 0;
2111
+ return encodeFrame(step);
2112
+ };
2113
+ let current;
2114
+ let written;
2115
+ const cursor = () => {
2116
+ if (current === void 0 || current === written) return "";
2117
+ written = current;
2118
+ return `${flushStep()}id: ${current}
2119
+
2120
+ `;
2121
+ };
2122
+ const shown = /* @__PURE__ */ new Set();
2123
+ let decision;
2062
2124
  const state = {
2063
2125
  closed: false,
2064
- push(event) {
2126
+ flush() {
2127
+ return state.closed ? "" : `${flushStep()}${cursor()}`;
2128
+ },
2129
+ push(event, id) {
2065
2130
  if (state.closed) return "";
2066
2131
  let out = "";
2132
+ if (id !== void 0) {
2133
+ if (current !== void 0 && id > current) out += cursor();
2134
+ current = id;
2135
+ }
2067
2136
  const frame = (streamEvent) => {
2068
- seq += 1;
2069
- out += `id: ${seq}
2070
- data: ${JSON.stringify(streamEvent)}
2071
-
2072
- `;
2137
+ out += encodeFrame(streamEvent);
2073
2138
  };
2139
+ if (event.type === "CUSTOM" && event.name === "agora.step-usage" && pendingStep !== void 0 && isRecord2(event.value)) {
2140
+ const value = event.value;
2141
+ if (isRecord2(value.usage)) pendingStep.usage = value.usage;
2142
+ if (typeof value.costUsd === "number" || value.costUsd === null) {
2143
+ pendingStep.costUsd = value.costUsd;
2144
+ }
2145
+ if (typeof value.reasoningMs === "number") pendingStep.reasoningMs = value.reasoningMs;
2146
+ out += flushStep();
2147
+ return out;
2148
+ }
2149
+ out += flushStep();
2074
2150
  const meta = (runId, thread) => {
2075
2151
  if (metaSent) return;
2076
2152
  metaSent = true;
@@ -2100,9 +2176,24 @@ data: ${JSON.stringify({
2100
2176
  frame({ kind: "title", title: value.title });
2101
2177
  } else if (event.name === "agora.queue") frame({ kind: "queue", ...value });
2102
2178
  else if (event.name === "agora.approval-requested") {
2179
+ if (typeof value.id === "string") shown.add(value.id);
2103
2180
  frame({ ...value, kind: "approval-requested" });
2104
2181
  } else if (event.name === "agora.approval-settled") {
2105
2182
  frame({ ...value, kind: "approval-settled" });
2183
+ } else if (event.name === "agora.elicitation" && typeof value.id === "string" && isRecord2(value.request) && Array.isArray(value.request.questions)) {
2184
+ shown.add(value.id);
2185
+ if (typeof value.request.id === "string") shown.add(value.request.id);
2186
+ frame({
2187
+ kind: "elicitation",
2188
+ id: value.id,
2189
+ request: value.request
2190
+ });
2191
+ } else if (event.name === "agora.action-proposal-decision") {
2192
+ decision = {
2193
+ threadId: value.threadId,
2194
+ proposalDecision: value.proposalDecision,
2195
+ text: ""
2196
+ };
2106
2197
  }
2107
2198
  return out;
2108
2199
  }
@@ -2112,6 +2203,9 @@ data: ${JSON.stringify({
2112
2203
  switch (event.type) {
2113
2204
  case "TEXT_MESSAGE_CONTENT":
2114
2205
  case "TEXT_MESSAGE_CHUNK":
2206
+ if (decision !== void 0 && typeof event.delta === "string") {
2207
+ decision.text += event.delta;
2208
+ }
2115
2209
  if (typeof event.delta === "string" && event.delta.length > 0) {
2116
2210
  frame({ kind: "text", text: event.delta });
2117
2211
  }
@@ -2126,20 +2220,34 @@ data: ${JSON.stringify({
2126
2220
  frame({ kind: "step-start" });
2127
2221
  break;
2128
2222
  case "STEP_FINISHED":
2129
- frame({ kind: "step-finish" });
2223
+ pendingStep = { kind: "step-finish" };
2130
2224
  break;
2131
2225
  case "TOOL_CALL_START":
2132
2226
  case "TOOL_CALL_CHUNK": {
2133
- const id = String(event.toolCallId ?? "");
2134
- if (id === "") break;
2135
- if (!tools.has(id)) {
2227
+ const id2 = String(event.toolCallId ?? "");
2228
+ if (id2 === "") break;
2229
+ if (!tools.has(id2)) {
2136
2230
  const name = typeof event.toolCallName === "string" ? event.toolCallName : "";
2137
- tools.set(id, { name, args: "" });
2138
- frame({ kind: "tool-input-start", id, name, toolKind: "read" });
2231
+ const metadata = isRecord2(event.metadata) ? event.metadata : {};
2232
+ const toolKind2 = metadata["agora.toolKind"] === "action" ? "action" : "read";
2233
+ const parentId = typeof metadata["agora.parentId"] === "string" ? metadata["agora.parentId"] : void 0;
2234
+ tools.set(id2, {
2235
+ name,
2236
+ args: "",
2237
+ toolKind: toolKind2,
2238
+ ...parentId !== void 0 ? { parentId } : {}
2239
+ });
2240
+ frame({
2241
+ kind: "tool-input-start",
2242
+ id: id2,
2243
+ name,
2244
+ toolKind: toolKind2,
2245
+ ...parentId !== void 0 ? { parentId } : {}
2246
+ });
2139
2247
  }
2140
2248
  if (event.type === "TOOL_CALL_CHUNK" && typeof event.delta === "string") {
2141
- tools.get(id).args += event.delta;
2142
- frame({ kind: "tool-input-delta", id, delta: event.delta });
2249
+ tools.get(id2).args += event.delta;
2250
+ frame({ kind: "tool-input-delta", id: id2, delta: event.delta });
2143
2251
  }
2144
2252
  break;
2145
2253
  }
@@ -2151,25 +2259,26 @@ data: ${JSON.stringify({
2151
2259
  break;
2152
2260
  }
2153
2261
  case "TOOL_CALL_END": {
2154
- const id = String(event.toolCallId);
2155
- const call = tools.get(id);
2262
+ const id2 = String(event.toolCallId);
2263
+ const call = tools.get(id2);
2156
2264
  if (call === void 0) break;
2157
2265
  frame({
2158
2266
  kind: "tool-input-available",
2159
- id,
2267
+ id: id2,
2160
2268
  name: call.name,
2161
2269
  input: parseJson(call.args, {}),
2162
- toolKind: "read"
2270
+ toolKind: call.toolKind,
2271
+ ...call.parentId !== void 0 ? { parentId: call.parentId } : {}
2163
2272
  });
2164
2273
  break;
2165
2274
  }
2166
2275
  case "TOOL_CALL_RESULT": {
2167
- const id = String(event.toolCallId);
2276
+ const id2 = String(event.toolCallId);
2168
2277
  const content = contentText(event.content);
2169
2278
  const outcome = isRecord2(event.metadata) ? event.metadata["agora.outcome"] : void 0;
2170
- if (outcome === "error") frame({ kind: "tool-output-error", id, error: content });
2171
- else if (outcome === "denied") frame({ kind: "tool-output-denied", id, reason: content });
2172
- else frame({ kind: "tool-output", id, output: parseJson(content, content) });
2279
+ if (outcome === "error") frame({ kind: "tool-output-error", id: id2, error: content });
2280
+ else if (outcome === "denied") frame({ kind: "tool-output-denied", id: id2, reason: content });
2281
+ else frame({ kind: "tool-output", id: id2, output: parseJson(content, content) });
2173
2282
  break;
2174
2283
  }
2175
2284
  case "ACTIVITY_SNAPSHOT":
@@ -2208,15 +2317,26 @@ data: ${JSON.stringify({
2208
2317
  break;
2209
2318
  case "RUN_FINISHED": {
2210
2319
  const outcome = isRecord2(event.outcome) ? event.outcome : void 0;
2320
+ if (decision !== void 0) {
2321
+ frame({ kind: "proposal-decision", ...decision });
2322
+ decision = void 0;
2323
+ }
2211
2324
  if (outcome?.type === "cancelled") frame({ kind: "cancelled" });
2212
2325
  if (outcome?.type === "interrupt" && Array.isArray(outcome.interrupts)) {
2326
+ const unseen = outcome.interrupts.filter((interrupt) => !isShown(interrupt, shown));
2327
+ if (unseen.length === 0) {
2328
+ out += cursor();
2329
+ state.closed = true;
2330
+ break;
2331
+ }
2213
2332
  frame({
2214
2333
  kind: "ui",
2215
2334
  id: `ag-ui:interrupt:${String(event.runId ?? "")}`,
2216
2335
  component: "AgUiInterrupt",
2217
- props: { interrupts: outcome.interrupts }
2336
+ props: { interrupts: unseen }
2218
2337
  });
2219
2338
  }
2339
+ out += cursor();
2220
2340
  state.closed = true;
2221
2341
  out += "event: done\ndata: {}\n\n";
2222
2342
  break;
@@ -2236,6 +2356,13 @@ data: ${JSON.stringify({
2236
2356
  let pendingStart;
2237
2357
  return state;
2238
2358
  }
2359
+ function isShown(interrupt, shown) {
2360
+ if (!isRecord2(interrupt)) return false;
2361
+ if (typeof interrupt.toolCallId === "string" && shown.has(interrupt.toolCallId)) return true;
2362
+ const metadata = isRecord2(interrupt.metadata) ? interrupt.metadata : void 0;
2363
+ const request = isRecord2(metadata?.["agora.request"]) ? metadata["agora.request"] : void 0;
2364
+ return typeof request?.id === "string" && shown.has(request.id);
2365
+ }
2239
2366
  function parseJson(text, fallback2) {
2240
2367
  if (text.trim().length === 0) return fallback2;
2241
2368
  try {