@assistant-ui/react-ag-ui 0.0.47 → 0.0.49

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.
Files changed (41) hide show
  1. package/dist/agUiExtras.d.ts +1 -1
  2. package/dist/agUiExtras.d.ts.map +1 -1
  3. package/dist/agUiExtras.js +1 -1
  4. package/dist/agUiExtras.js.map +1 -1
  5. package/dist/hooks.d.ts +6 -1
  6. package/dist/hooks.d.ts.map +1 -1
  7. package/dist/hooks.js +10 -1
  8. package/dist/hooks.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +2 -2
  11. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +3 -0
  12. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  13. package/dist/runtime/AgUiThreadRuntimeCore.js +78 -16
  14. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  15. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  16. package/dist/runtime/adapter/conversions.js +63 -1
  17. package/dist/runtime/adapter/conversions.js.map +1 -1
  18. package/dist/runtime/adapter/run-aggregator.d.ts +6 -1
  19. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  20. package/dist/runtime/adapter/run-aggregator.js +57 -1
  21. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  22. package/dist/runtime/event-parser.js +5 -2
  23. package/dist/runtime/event-parser.js.map +1 -1
  24. package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
  25. package/dist/runtime/mcp-tool-result.js +6 -3
  26. package/dist/runtime/mcp-tool-result.js.map +1 -1
  27. package/dist/runtime/types.d.ts +3 -0
  28. package/dist/runtime/types.d.ts.map +1 -1
  29. package/dist/useAgUiRuntime.js +2 -1
  30. package/dist/useAgUiRuntime.js.map +1 -1
  31. package/package.json +5 -4
  32. package/src/agUiExtras.ts +1 -1
  33. package/src/hooks.ts +14 -0
  34. package/src/index.ts +1 -0
  35. package/src/runtime/AgUiThreadRuntimeCore.ts +132 -16
  36. package/src/runtime/adapter/conversions.ts +107 -1
  37. package/src/runtime/adapter/run-aggregator.ts +82 -0
  38. package/src/runtime/event-parser.ts +12 -5
  39. package/src/runtime/mcp-tool-result.ts +8 -6
  40. package/src/runtime/types.ts +3 -0
  41. package/src/useAgUiRuntime.ts +1 -0
@@ -8,6 +8,12 @@ import {
8
8
  type ToolCallMessagePart,
9
9
  type ToolModelContentPart,
10
10
  } from "@assistant-ui/core";
11
+ import {
12
+ applyA2uiOperations,
13
+ convertSurfaceToUISpec,
14
+ type A2uiState,
15
+ type A2uiSurfaceState,
16
+ } from "@assistant-ui/react-generative-ui/a2ui";
11
17
  import { readMcpAppResourceUri } from "../mcp-tool-result";
12
18
  import type { AgUiEvent, AgUiInterrupt } from "../types";
13
19
  import type { Logger } from "../logger";
@@ -37,6 +43,8 @@ type ToolCallState = {
37
43
 
38
44
  export const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
39
45
 
46
+ export const A2UI_SURFACE_ACTIVITY_TYPE = "a2ui-surface";
47
+
40
48
  export const isPlainObject = (
41
49
  value: unknown,
42
50
  ): value is Record<string, unknown> =>
@@ -72,6 +80,8 @@ export class RunAggregator {
72
80
  private activeReasoningKey: string | undefined;
73
81
  private reasoningPartCounter = 0;
74
82
  private readonly toolCalls = new Map<string, ToolCallState>();
83
+ private readonly a2uiBuckets = new Map<string, A2uiState>();
84
+ private readonly a2uiToolCallIds = new Set<string>();
75
85
  private lastResolvedToolCallId: string | undefined;
76
86
  private readonly partOrder: (
77
87
  | { kind: "text"; key: string }
@@ -104,6 +114,8 @@ export class RunAggregator {
104
114
  this.activeReasoningKey = undefined;
105
115
  this.reasoningPartCounter = 0;
106
116
  this.toolCalls.clear();
117
+ this.a2uiBuckets.clear();
118
+ this.a2uiToolCallIds.clear();
107
119
  this.lastResolvedToolCallId = undefined;
108
120
  this.partOrder.length = 0;
109
121
  this.textPartCounter = 0;
@@ -250,6 +262,10 @@ export class RunAggregator {
250
262
  break;
251
263
  }
252
264
  case "ACTIVITY_SNAPSHOT": {
265
+ if (event.activityType === A2UI_SURFACE_ACTIVITY_TYPE) {
266
+ this.handleA2uiActivitySnapshot(event);
267
+ break;
268
+ }
253
269
  if (event.activityType !== MCP_APPS_ACTIVITY_TYPE) break;
254
270
  const toolCallId = event.content.toolCallId;
255
271
  const entry =
@@ -305,6 +321,72 @@ export class RunAggregator {
305
321
  }
306
322
  }
307
323
 
324
+ private handleA2uiActivitySnapshot(
325
+ event: Extract<AgUiEvent, { type: "ACTIVITY_SNAPSHOT" }>,
326
+ ): void {
327
+ const operations = event.content["a2ui_operations"];
328
+ if (!Array.isArray(operations)) return;
329
+
330
+ const messageId = event.messageId ?? "a2ui:anonymous";
331
+ if (event.replace === false && this.a2uiBuckets.has(messageId)) return;
332
+
333
+ const { state, warnings } = applyA2uiOperations(new Map(), operations);
334
+ for (const warning of warnings) {
335
+ this.logger.debug("[agui] a2ui operation warning", warning);
336
+ }
337
+ this.a2uiBuckets.delete(messageId);
338
+ this.a2uiBuckets.set(messageId, state);
339
+ this.synthesizeA2uiToolCalls();
340
+ this.emit();
341
+ }
342
+
343
+ private synthesizeA2uiToolCalls(): void {
344
+ const surfaces = new Map<string, A2uiSurfaceState>();
345
+
346
+ for (const bucket of this.a2uiBuckets.values()) {
347
+ for (const [surfaceId, surface] of bucket) {
348
+ surfaces.set(surfaceId, surface);
349
+ }
350
+ }
351
+
352
+ const activeToolCallIds = new Set<string>();
353
+ for (const [surfaceId, surface] of surfaces) {
354
+ const toolCallId = `a2ui:${surfaceId}`;
355
+ const { spec, warnings } = convertSurfaceToUISpec(surface);
356
+ for (const warning of warnings) {
357
+ this.logger.debug("[agui] a2ui surface conversion warning", warning);
358
+ }
359
+ if (!spec) continue;
360
+
361
+ activeToolCallIds.add(toolCallId);
362
+
363
+ const entry: ToolCallState = {
364
+ toolCallId,
365
+ toolCallName: "present",
366
+ argsText: JSON.stringify(spec),
367
+ parsedArgs: spec,
368
+ result: {},
369
+ isError: undefined,
370
+ snapshotResultApplied: false,
371
+ };
372
+ if (!this.toolCalls.has(toolCallId)) {
373
+ this.partOrder.push({ kind: "tool-call", toolCallId });
374
+ }
375
+ this.toolCalls.set(toolCallId, entry);
376
+ this.a2uiToolCallIds.add(toolCallId);
377
+ }
378
+
379
+ for (const toolCallId of this.a2uiToolCallIds) {
380
+ if (activeToolCallIds.has(toolCallId)) continue;
381
+ this.toolCalls.delete(toolCallId);
382
+ const partIndex = this.partOrder.findIndex(
383
+ (part) => part.kind === "tool-call" && part.toolCallId === toolCallId,
384
+ );
385
+ if (partIndex !== -1) this.partOrder.splice(partIndex, 1);
386
+ this.a2uiToolCallIds.delete(toolCallId);
387
+ }
388
+ }
389
+
308
390
  private reportServerMessageId(messageId: string | undefined): void {
309
391
  if (this.serverMessageIdReported || !messageId) return;
310
392
  this.serverMessageIdReported = true;
@@ -241,11 +241,18 @@ export const parseAgUiEvent = (
241
241
  case "ACTIVITY_SNAPSHOT": {
242
242
  const activityType = getString("activityType");
243
243
  if (!activityType || !isPlainObject(payload.content)) return null;
244
- return {
245
- type: "ACTIVITY_SNAPSHOT" as const,
246
- activityType,
247
- content: payload.content,
248
- };
244
+ return withOptional(
245
+ {
246
+ type: "ACTIVITY_SNAPSHOT" as const,
247
+ activityType,
248
+ content: payload.content,
249
+ },
250
+ {
251
+ messageId: getString("messageId"),
252
+ replace:
253
+ typeof payload.replace === "boolean" ? payload.replace : undefined,
254
+ },
255
+ );
249
256
  }
250
257
  case "RAW":
251
258
  return withOptional(
@@ -26,12 +26,14 @@ export const parseMcpToolCallResult = (
26
26
  };
27
27
  };
28
28
 
29
- // _meta["ui/resourceUri"] is the MCP-UI pointer react-ai-sdk reads off
30
- // CallToolResult._meta; only ui:// URIs identify an MCP Apps widget. A
31
- // structured carrier is deliberately not read until upstream AG-UI settles
32
- // one (agno-agi/agno#9087).
29
+ // The MCP Apps pointer on CallToolResult._meta: canonical nested `ui.resourceUri`
30
+ // per the Apps spec (2026-01-26), with the deprecated flat `"ui/resourceUri"` kept
31
+ // for legacy producers. Only ui:// URIs identify a widget.
33
32
  export const readMcpAppResourceUri = (meta: unknown): string | undefined => {
34
33
  if (!isRecord(meta)) return undefined;
35
- const uri = meta["ui/resourceUri"];
36
- return typeof uri === "string" && isMcpAppUri(uri) ? uri : undefined;
34
+ const ui = meta["ui"];
35
+ const nested = isRecord(ui) ? ui["resourceUri"] : undefined;
36
+ if (typeof nested === "string" && isMcpAppUri(nested)) return nested;
37
+ const flat = meta["ui/resourceUri"];
38
+ return typeof flat === "string" && isMcpAppUri(flat) ? flat : undefined;
37
39
  };
@@ -100,6 +100,7 @@ export type AgUiResumeEntry = {
100
100
 
101
101
  export type AgUiRuntimeExtras = {
102
102
  interrupts: readonly AgUiInterrupt[];
103
+ sendA2uiAction: (action: Record<string, unknown>) => void;
103
104
  submitInterruptResponses: (
104
105
  responses: readonly AgUiResumeEntry[],
105
106
  ) => Promise<void>;
@@ -169,6 +170,8 @@ export type AgUiEvent =
169
170
  type: "ACTIVITY_SNAPSHOT";
170
171
  activityType: string;
171
172
  content: Record<string, unknown>;
173
+ messageId?: string;
174
+ replace?: boolean;
172
175
  }
173
176
  | { type: "RAW"; event: any; source?: string }
174
177
  | { type: "CUSTOM"; name: string; value: any }
@@ -147,6 +147,7 @@ export function useAgUiRuntime(
147
147
  extras: agUiExtras.provide({
148
148
  interrupts:
149
149
  core.getPendingInterrupts()?.interrupts ?? EMPTY_INTERRUPTS,
150
+ sendA2uiAction: (action) => core.sendA2uiAction(action),
150
151
  submitInterruptResponses: (responses) =>
151
152
  core.submitInterruptResponses(responses),
152
153
  steerAway: (message, responses) => core.steerAway(message, responses),