@assistant-ui/react-ag-ui 0.0.45 → 0.0.47
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/runtime/AgUiThreadRuntimeCore.d.ts +2 -0
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +86 -25
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +18 -15
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +3 -1
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +18 -5
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +5 -2
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/mcp-tool-result.d.ts +12 -0
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -0
- package/dist/runtime/mcp-tool-result.js +26 -0
- package/dist/runtime/mcp-tool-result.js.map +1 -0
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/runtime/AgUiThreadRuntimeCore.ts +162 -39
- package/src/runtime/adapter/conversions.ts +28 -16
- package/src/runtime/adapter/run-aggregator.ts +22 -4
- package/src/runtime/event-parser.ts +4 -1
- package/src/runtime/mcp-tool-result.ts +37 -0
- package/src/runtime/types.ts +2 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isRecord } from "@assistant-ui/core/internal";
|
|
2
|
+
import { isMcpAppUri } from "@assistant-ui/core";
|
|
3
|
+
//#region src/runtime/mcp-tool-result.ts
|
|
4
|
+
const parseMcpToolCallResult = (source, modelContent) => {
|
|
5
|
+
const structuredContent = isRecord(source.structuredContent) ? source.structuredContent : void 0;
|
|
6
|
+
const meta = isRecord(source._meta) ? source._meta : void 0;
|
|
7
|
+
if (!structuredContent && !meta) return void 0;
|
|
8
|
+
return {
|
|
9
|
+
content: modelContent ? [{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: modelContent
|
|
12
|
+
}] : [],
|
|
13
|
+
...structuredContent ? { structuredContent } : {},
|
|
14
|
+
...meta ? { _meta: meta } : {},
|
|
15
|
+
...typeof source.isError === "boolean" ? { isError: source.isError } : {}
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
const readMcpAppResourceUri = (meta) => {
|
|
19
|
+
if (!isRecord(meta)) return void 0;
|
|
20
|
+
const uri = meta["ui/resourceUri"];
|
|
21
|
+
return typeof uri === "string" && isMcpAppUri(uri) ? uri : void 0;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { parseMcpToolCallResult, readMcpAppResourceUri };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=mcp-tool-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tool-result.js","names":[],"sources":["../../src/runtime/mcp-tool-result.ts"],"sourcesContent":["import { isRecord } from \"@assistant-ui/core/internal\";\nimport { isMcpAppUri } from \"@assistant-ui/core\";\n\nexport type McpToolCallResult = Record<string, unknown> & {\n content: unknown[];\n structuredContent?: Record<string, unknown>;\n _meta?: Record<string, unknown>;\n isError?: boolean;\n};\n\nexport const parseMcpToolCallResult = (\n source: Record<string, unknown>,\n modelContent: string,\n): McpToolCallResult | undefined => {\n const structuredContent = isRecord(source.structuredContent)\n ? source.structuredContent\n : undefined;\n const meta = isRecord(source._meta) ? source._meta : undefined;\n if (!structuredContent && !meta) return undefined;\n\n return {\n content: modelContent ? [{ type: \"text\", text: modelContent }] : [],\n ...(structuredContent ? { structuredContent } : {}),\n ...(meta ? { _meta: meta } : {}),\n ...(typeof source.isError === \"boolean\" ? { isError: source.isError } : {}),\n };\n};\n\n// _meta[\"ui/resourceUri\"] is the MCP-UI pointer react-ai-sdk reads off\n// CallToolResult._meta; only ui:// URIs identify an MCP Apps widget. A\n// structured carrier is deliberately not read until upstream AG-UI settles\n// one (agno-agi/agno#9087).\nexport const readMcpAppResourceUri = (meta: unknown): string | undefined => {\n if (!isRecord(meta)) return undefined;\n const uri = meta[\"ui/resourceUri\"];\n return typeof uri === \"string\" && isMcpAppUri(uri) ? uri : undefined;\n};\n"],"mappings":";;;AAUA,MAAa,0BACX,QACA,iBACkC;CAClC,MAAM,oBAAoB,SAAS,OAAO,iBAAiB,IACvD,OAAO,oBACP,KAAA;CACJ,MAAM,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,QAAQ,KAAA;CACrD,IAAI,CAAC,qBAAqB,CAAC,MAAM,OAAO,KAAA;CAExC,OAAO;EACL,SAAS,eAAe,CAAC;GAAE,MAAM;GAAQ,MAAM;EAAa,CAAC,IAAI,CAAC;EAClE,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;EACjD,GAAI,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;EAC9B,GAAI,OAAO,OAAO,YAAY,YAAY,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CAC3E;AACF;AAMA,MAAa,yBAAyB,SAAsC;CAC1E,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAA;CAC5B,MAAM,MAAM,KAAK;CACjB,OAAO,OAAO,QAAQ,YAAY,YAAY,GAAG,IAAI,MAAM,KAAA;AAC7D"}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger } from "./logger.js";
|
|
2
|
+
import { McpToolCallResult } from "./mcp-tool-result.js";
|
|
2
3
|
import { AttachmentAdapter, CreateAppendMessage, DictationAdapter, ExternalStoreSharedOptions, ExternalStoreThreadListAdapter, FeedbackAdapter, RealtimeVoiceAdapter, SpeechSynthesisAdapter, ThreadHistoryAdapter, ThreadMessage } from "@assistant-ui/core";
|
|
3
4
|
import { AbstractAgent } from "@ag-ui/client";
|
|
4
5
|
import { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
@@ -160,6 +161,7 @@ type AgUiEvent = {
|
|
|
160
161
|
toolCallId: string;
|
|
161
162
|
content: string;
|
|
162
163
|
role?: "tool";
|
|
164
|
+
mcpResult?: McpToolCallResult;
|
|
163
165
|
} | {
|
|
164
166
|
type: "ACTIVITY_SNAPSHOT";
|
|
165
167
|
activityType: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/runtime/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/runtime/types.ts"],"mappings":";;;;;;;;;;;;KAuBK;EACH,mBAAmB;EACnB,QAAQ;;;;;;EAMR;;KAGU,2BAA2B,KACrC;EAGA,qBAEM,qBACG,QAAQ,wBAAwB;;KAI/B;EACV,cAAc;EACd,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,UAAU;;;;EAIV,aAAa;;KAGH,wBAAwB;EAClC,OAAO;EACP,SAAS,QAAQ;EACjB;;;;;;;;;;;EAWA;EACA,WAAW,GAAG;EACd;EACA,WAAW;;KAGD;KAMA;EACV;EACA,QAAQ;EACR;EACA;EACA,iBAAiB;EACjB;EACA,WAAW;;KAGD;EACV;EACA;EACA;;KAGU;EACV,qBAAqB;EACrB,2BACE,oBAAoB,sBACjB;EACL,YACE,SAAS,qBACT,qBAAqB,sBAClB;EACL,OAAO;EACP,WACE,MACI,sBACE,MAAM,kCAAkC;;KAItC;EACN;;EACA;EAAmB,YAAY;;KAEzB;EACN;EAAqB;;EAErB;EACA;EACA,UAAU;;EAEV;EAAuB;;EACvB;EAAmB;EAAkB;;EACrC;EAA4B;;EAC5B;EAA8B;EAAoB;;EAClD;EAA0B;;EAC1B;EAA4B;;EAC5B;EAAwB;;EACxB;;EACA;EAAuC;;EACvC;;EACA;;EACA;EAAyB;;EACzB;EAAiC;;EACjC;EAAmC;EAAoB;;EACvD;EAA+B;;EAC/B;EAAuB;;EAEvB;EACA;EACA;EACA;;EAEA;EAAwB;EAAoB;;EAC5C;EAAuB;;EAEvB;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;EACA;EACA,YAAY;;EAGZ;EACA;EACA,SAAS;;EAET;EAAa;EAAY;;EACzB;EAAgB;EAAc;;EAC9B;EAAwB;;EACxB;EAAqB;;EACrB;EAA2B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-ag-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "AG-UI protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ag-ui",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ag-ui/client": "^0.0.57",
|
|
33
|
-
"@assistant-ui/core": "^0.2.
|
|
34
|
-
"@assistant-ui/store": "^0.2.
|
|
35
|
-
"assistant-stream": "^0.3.
|
|
33
|
+
"@assistant-ui/core": "^0.2.23",
|
|
34
|
+
"@assistant-ui/store": "^0.2.22",
|
|
35
|
+
"assistant-stream": "^0.3.28",
|
|
36
36
|
"fast-json-patch": "^3.1.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^19.2.17",
|
|
49
|
-
"react": "^19.2.
|
|
49
|
+
"react": "^19.2.8",
|
|
50
50
|
"vitest": "^4.1.10",
|
|
51
51
|
"zod": "^4.4.3",
|
|
52
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
52
|
+
"@assistant-ui/x-buildutils": "0.0.20"
|
|
53
53
|
},
|
|
54
54
|
"publishConfig": {
|
|
55
55
|
"access": "public",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
generateId,
|
|
5
|
+
fromThreadMessageLike,
|
|
6
|
+
isMcpAppUri,
|
|
7
|
+
} from "@assistant-ui/core";
|
|
4
8
|
import type {
|
|
5
9
|
AddToolResultOptions,
|
|
6
10
|
AppendMessage,
|
|
@@ -19,11 +23,14 @@ import { MessageRepository } from "@assistant-ui/core/internal";
|
|
|
19
23
|
import type { AbstractAgent } from "@ag-ui/client";
|
|
20
24
|
import jsonpatch, { type Operation } from "fast-json-patch";
|
|
21
25
|
import type { Logger } from "./logger";
|
|
26
|
+
import { readMcpAppResourceUri } from "./mcp-tool-result";
|
|
22
27
|
import type { AgUiEvent, AgUiInterrupt, AgUiResumeEntry } from "./types";
|
|
23
28
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
24
29
|
import {
|
|
25
30
|
AG_UI_METADATA_NAMESPACE,
|
|
26
31
|
type AgUiCustomMetadata,
|
|
32
|
+
isPlainObject,
|
|
33
|
+
MCP_APPS_ACTIVITY_TYPE,
|
|
27
34
|
RunAggregator,
|
|
28
35
|
tryParseJSON,
|
|
29
36
|
} from "./adapter/run-aggregator";
|
|
@@ -425,25 +432,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
425
432
|
);
|
|
426
433
|
}
|
|
427
434
|
|
|
428
|
-
const responsesById =
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
if (entry.status !== "resolved" && entry.status !== "cancelled") {
|
|
436
|
-
throw new Error(
|
|
437
|
-
`[agui] submitInterruptResponses: invalid status "${entry.status}" for interrupt ${entry.interruptId}`,
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
if (responsesById.has(entry.interruptId)) {
|
|
441
|
-
throw new Error(
|
|
442
|
-
`[agui] submitInterruptResponses: duplicate response for interrupt ${entry.interruptId}`,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
responsesById.set(entry.interruptId, entry);
|
|
446
|
-
}
|
|
435
|
+
const responsesById = this.collectInterruptResponses(
|
|
436
|
+
"submitInterruptResponses",
|
|
437
|
+
pending.interrupts,
|
|
438
|
+
responses,
|
|
439
|
+
);
|
|
447
440
|
|
|
448
441
|
const openIds = pending.interrupts.map((i) => i.id);
|
|
449
442
|
const missing = openIds.filter((id) => !responsesById.has(id));
|
|
@@ -452,13 +445,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
452
445
|
`[agui] submitInterruptResponses: missing responses for open interrupts: ${missing.join(", ")}`,
|
|
453
446
|
);
|
|
454
447
|
}
|
|
455
|
-
const known = new Set(openIds);
|
|
456
|
-
const unknownIds = [...responsesById.keys()].filter((id) => !known.has(id));
|
|
457
|
-
if (unknownIds.length > 0) {
|
|
458
|
-
throw new Error(
|
|
459
|
-
`[agui] submitInterruptResponses: unknown interrupt ids: ${unknownIds.join(", ")}`,
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
448
|
|
|
463
449
|
const now = Date.now();
|
|
464
450
|
for (const interrupt of pending.interrupts) {
|
|
@@ -538,35 +524,51 @@ export class AgUiThreadRuntimeCore {
|
|
|
538
524
|
interrupts: readonly AgUiInterrupt[],
|
|
539
525
|
responses: readonly AgUiResumeEntry[] | undefined,
|
|
540
526
|
): AgUiResumeEntry[] {
|
|
541
|
-
const
|
|
542
|
-
|
|
527
|
+
const responsesById = this.collectInterruptResponses(
|
|
528
|
+
"steerAway",
|
|
529
|
+
interrupts,
|
|
530
|
+
responses ?? [],
|
|
531
|
+
);
|
|
532
|
+
return interrupts.map(
|
|
533
|
+
(interrupt) =>
|
|
534
|
+
responsesById.get(interrupt.id) ?? {
|
|
535
|
+
interruptId: interrupt.id,
|
|
536
|
+
status: "cancelled",
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private collectInterruptResponses(
|
|
542
|
+
method: "submitInterruptResponses" | "steerAway",
|
|
543
|
+
interrupts: readonly AgUiInterrupt[],
|
|
544
|
+
responses: readonly AgUiResumeEntry[],
|
|
545
|
+
): Map<string, AgUiResumeEntry> {
|
|
546
|
+
const known = new Set(interrupts.map((interrupt) => interrupt.id));
|
|
543
547
|
const responsesById = new Map<string, AgUiResumeEntry>();
|
|
544
|
-
for (const entry of responses
|
|
548
|
+
for (const entry of responses) {
|
|
545
549
|
if (!entry || typeof entry.interruptId !== "string") {
|
|
546
550
|
throw new Error(
|
|
547
|
-
|
|
551
|
+
`[agui] ${method}: every entry must have an interruptId`,
|
|
548
552
|
);
|
|
549
553
|
}
|
|
550
554
|
if (entry.status !== "resolved" && entry.status !== "cancelled") {
|
|
551
555
|
throw new Error(
|
|
552
|
-
`[agui]
|
|
556
|
+
`[agui] ${method}: invalid status "${entry.status}" for interrupt ${entry.interruptId}`,
|
|
553
557
|
);
|
|
554
558
|
}
|
|
555
559
|
if (!known.has(entry.interruptId)) {
|
|
556
560
|
throw new Error(
|
|
557
|
-
`[agui]
|
|
561
|
+
`[agui] ${method}: unknown interrupt id ${entry.interruptId}`,
|
|
558
562
|
);
|
|
559
563
|
}
|
|
560
564
|
if (responsesById.has(entry.interruptId)) {
|
|
561
565
|
throw new Error(
|
|
562
|
-
`[agui]
|
|
566
|
+
`[agui] ${method}: duplicate response for interrupt ${entry.interruptId}`,
|
|
563
567
|
);
|
|
564
568
|
}
|
|
565
569
|
responsesById.set(entry.interruptId, entry);
|
|
566
570
|
}
|
|
567
|
-
return
|
|
568
|
-
(id) => responsesById.get(id) ?? { interruptId: id, status: "cancelled" },
|
|
569
|
-
);
|
|
571
|
+
return responsesById;
|
|
570
572
|
}
|
|
571
573
|
|
|
572
574
|
private toAppendMessage(message: CreateAppendMessage): AppendMessage {
|
|
@@ -1344,6 +1346,22 @@ export class AgUiThreadRuntimeCore {
|
|
|
1344
1346
|
aggregator.handle(event);
|
|
1345
1347
|
return;
|
|
1346
1348
|
}
|
|
1349
|
+
case "ACTIVITY_SNAPSHOT": {
|
|
1350
|
+
const toolCallId = event.content["toolCallId"];
|
|
1351
|
+
if (
|
|
1352
|
+
event.activityType === MCP_APPS_ACTIVITY_TYPE &&
|
|
1353
|
+
typeof toolCallId === "string" &&
|
|
1354
|
+
!aggregator.hasToolCall(toolCallId)
|
|
1355
|
+
) {
|
|
1356
|
+
const messageId = this.findMessageIdForToolCall(toolCallId);
|
|
1357
|
+
if (messageId !== undefined) {
|
|
1358
|
+
this.applyCrossRunActivitySnapshot(messageId, toolCallId, event);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
aggregator.handle(event);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1347
1365
|
default:
|
|
1348
1366
|
aggregator.handle(event);
|
|
1349
1367
|
}
|
|
@@ -1356,15 +1374,58 @@ export class AgUiThreadRuntimeCore {
|
|
|
1356
1374
|
const updated = this.updateMessage(messageId, (message) => {
|
|
1357
1375
|
if (message.role !== "assistant") return message;
|
|
1358
1376
|
const assistant = message as ThreadAssistantMessage;
|
|
1377
|
+
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
1359
1378
|
let matchedToolCall = false;
|
|
1360
1379
|
const content = assistant.content.map((part) => {
|
|
1361
1380
|
if (part.type !== "tool-call" || part.toolCallId !== event.toolCallId)
|
|
1362
1381
|
return part;
|
|
1363
1382
|
matchedToolCall = true;
|
|
1383
|
+
// An applied activity snapshot owns part.result; a later result only
|
|
1384
|
+
// fills what is missing, mirroring the aggregator's finishToolCall.
|
|
1385
|
+
// The aggregator's flag is set only when the snapshot carried a
|
|
1386
|
+
// result, so app presence alone is not enough: the current result
|
|
1387
|
+
// must be CallToolResult-shaped (a required content array).
|
|
1388
|
+
const snapshotResultApplied =
|
|
1389
|
+
part.mcp?.app !== undefined &&
|
|
1390
|
+
isPlainObject(part.result) &&
|
|
1391
|
+
Array.isArray((part.result as Record<string, unknown>)["content"]);
|
|
1392
|
+
if (snapshotResultApplied) {
|
|
1393
|
+
return {
|
|
1394
|
+
...part,
|
|
1395
|
+
...(part.modelContent === undefined && event.content
|
|
1396
|
+
? {
|
|
1397
|
+
modelContent: [
|
|
1398
|
+
{ type: "text" as const, text: event.content },
|
|
1399
|
+
],
|
|
1400
|
+
}
|
|
1401
|
+
: {}),
|
|
1402
|
+
...(part.isError === undefined
|
|
1403
|
+
? typeof event.mcpResult?.isError === "boolean"
|
|
1404
|
+
? { isError: event.mcpResult.isError }
|
|
1405
|
+
: event.role === "tool"
|
|
1406
|
+
? { isError: false }
|
|
1407
|
+
: {}
|
|
1408
|
+
: {}),
|
|
1409
|
+
...(event.messageId
|
|
1410
|
+
? { unstable_toolMessageId: event.messageId }
|
|
1411
|
+
: {}),
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1364
1414
|
return {
|
|
1365
1415
|
...part,
|
|
1366
|
-
result:
|
|
1367
|
-
|
|
1416
|
+
result: (event.mcpResult ??
|
|
1417
|
+
tryParseJSON(event.content ?? "")) as ReadonlyJSONValue,
|
|
1418
|
+
...(event.mcpResult !== undefined
|
|
1419
|
+
? { modelContent: [{ type: "text" as const, text: event.content }] }
|
|
1420
|
+
: {}),
|
|
1421
|
+
...(typeof event.mcpResult?.isError === "boolean"
|
|
1422
|
+
? { isError: event.mcpResult.isError }
|
|
1423
|
+
: event.role === "tool"
|
|
1424
|
+
? { isError: false }
|
|
1425
|
+
: {}),
|
|
1426
|
+
...(part.mcp === undefined && mcpAppUri !== undefined
|
|
1427
|
+
? { mcp: { app: { resourceUri: mcpAppUri } } }
|
|
1428
|
+
: {}),
|
|
1368
1429
|
...(event.messageId
|
|
1369
1430
|
? { unstable_toolMessageId: event.messageId }
|
|
1370
1431
|
: {}),
|
|
@@ -1381,6 +1442,68 @@ export class AgUiThreadRuntimeCore {
|
|
|
1381
1442
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1382
1443
|
}
|
|
1383
1444
|
|
|
1445
|
+
private applyCrossRunActivitySnapshot(
|
|
1446
|
+
messageId: string,
|
|
1447
|
+
toolCallId: string,
|
|
1448
|
+
event: Extract<AgUiEvent, { type: "ACTIVITY_SNAPSHOT" }>,
|
|
1449
|
+
): void {
|
|
1450
|
+
const resourceUri = event.content["resourceUri"];
|
|
1451
|
+
if (typeof resourceUri !== "string" || !isMcpAppUri(resourceUri)) return;
|
|
1452
|
+
const serverId = event.content["serverId"];
|
|
1453
|
+
const serverHash = event.content["serverHash"];
|
|
1454
|
+
const appServerId =
|
|
1455
|
+
typeof serverId === "string" && serverId.length > 0
|
|
1456
|
+
? serverId
|
|
1457
|
+
: typeof serverHash === "string" && serverHash.length > 0
|
|
1458
|
+
? serverHash
|
|
1459
|
+
: undefined;
|
|
1460
|
+
const result = event.content["result"];
|
|
1461
|
+
const updated = this.updateMessage(messageId, (message) => {
|
|
1462
|
+
if (message.role !== "assistant") return message;
|
|
1463
|
+
const assistant = message as ThreadAssistantMessage;
|
|
1464
|
+
let matchedToolCall = false;
|
|
1465
|
+
const content = assistant.content.map((part) => {
|
|
1466
|
+
if (part.type !== "tool-call" || part.toolCallId !== toolCallId)
|
|
1467
|
+
return part;
|
|
1468
|
+
matchedToolCall = true;
|
|
1469
|
+
return {
|
|
1470
|
+
...part,
|
|
1471
|
+
mcp: {
|
|
1472
|
+
app: {
|
|
1473
|
+
resourceUri,
|
|
1474
|
+
...(appServerId ? { serverId: appServerId } : {}),
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
...(isPlainObject(result)
|
|
1478
|
+
? {
|
|
1479
|
+
...(part.result !== undefined && part.modelContent === undefined
|
|
1480
|
+
? {
|
|
1481
|
+
modelContent: [
|
|
1482
|
+
{
|
|
1483
|
+
type: "text" as const,
|
|
1484
|
+
text:
|
|
1485
|
+
typeof part.result === "string"
|
|
1486
|
+
? part.result
|
|
1487
|
+
: JSON.stringify(part.result),
|
|
1488
|
+
},
|
|
1489
|
+
],
|
|
1490
|
+
}
|
|
1491
|
+
: {}),
|
|
1492
|
+
result: result as ReadonlyJSONValue,
|
|
1493
|
+
isError: result["isError"] === true,
|
|
1494
|
+
}
|
|
1495
|
+
: {}),
|
|
1496
|
+
};
|
|
1497
|
+
});
|
|
1498
|
+
if (!matchedToolCall) return message;
|
|
1499
|
+
return { ...assistant, content };
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
if (!updated) return;
|
|
1503
|
+
this.notifyUpdate();
|
|
1504
|
+
this.maybeCompleteAfterToolResults(messageId);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1384
1507
|
private importMessagesSnapshot(rawMessages: readonly unknown[]) {
|
|
1385
1508
|
try {
|
|
1386
1509
|
const normalized = fromAgUiMessages(rawMessages, {
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
import type { InputContent } from "@ag-ui/client";
|
|
4
4
|
import type {
|
|
5
5
|
ThreadMessageLike as CoreThreadMessageLike,
|
|
6
|
+
ToolCallMessagePartMcpMetadata,
|
|
6
7
|
ToolModelContentPart,
|
|
7
8
|
} from "@assistant-ui/core";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getAutoStatus,
|
|
11
|
+
httpUrlPattern,
|
|
12
|
+
parseDataUrl,
|
|
13
|
+
} from "@assistant-ui/core/internal";
|
|
9
14
|
import { type Tool, toToolsJSONSchema } from "assistant-stream";
|
|
10
15
|
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
11
16
|
import {
|
|
@@ -13,6 +18,10 @@ import {
|
|
|
13
18
|
type AgUiCustomMetadata,
|
|
14
19
|
} from "./run-aggregator";
|
|
15
20
|
import type { AgUiInterrupt } from "../types";
|
|
21
|
+
import {
|
|
22
|
+
parseMcpToolCallResult,
|
|
23
|
+
readMcpAppResourceUri,
|
|
24
|
+
} from "../mcp-tool-result";
|
|
16
25
|
|
|
17
26
|
export type { InputContent };
|
|
18
27
|
|
|
@@ -64,6 +73,7 @@ type ToolCallPart = {
|
|
|
64
73
|
isError?: boolean;
|
|
65
74
|
modelContent?: readonly ToolModelContentPart[];
|
|
66
75
|
unstable_toolMessageId?: string;
|
|
76
|
+
mcp?: ToolCallMessagePartMcpMetadata;
|
|
67
77
|
};
|
|
68
78
|
|
|
69
79
|
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
@@ -129,16 +139,6 @@ function extractText(content: unknown): string {
|
|
|
129
139
|
.join("\n");
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
function parseDataUrl(
|
|
133
|
-
value: string,
|
|
134
|
-
): { mimeType: string; data: string } | null {
|
|
135
|
-
const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.+)$/);
|
|
136
|
-
if (!match) return null;
|
|
137
|
-
return { mimeType: match[1]!, data: match[2]! };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const httpUrlPattern = /^https?:\/\//i;
|
|
141
|
-
|
|
142
142
|
type InputContentSource =
|
|
143
143
|
| { type: "data"; value: string; mimeType: string }
|
|
144
144
|
| { type: "url"; value: string; mimeType?: string };
|
|
@@ -540,12 +540,18 @@ export function fromAgUiMessages(
|
|
|
540
540
|
if (role === "tool") {
|
|
541
541
|
const toolCallId = getToolCallId(rawMessage) ?? `tool-${generateId()}`;
|
|
542
542
|
const toolMessageId = getString(rawMessage, "id");
|
|
543
|
+
const modelContent = extractText(rawMessage.content);
|
|
544
|
+
const mcpResult = parseMcpToolCallResult(rawMessage, modelContent);
|
|
545
|
+
const mcpModelContent = mcpResult
|
|
546
|
+
? [{ type: "text" as const, text: modelContent }]
|
|
547
|
+
: undefined;
|
|
543
548
|
const result =
|
|
544
|
-
|
|
549
|
+
mcpResult ??
|
|
550
|
+
(rawMessage.result !== undefined
|
|
545
551
|
? rawMessage.result
|
|
546
552
|
: typeof rawMessage.content === "string"
|
|
547
553
|
? parseJSONText(rawMessage.content)
|
|
548
|
-
: rawMessage.content;
|
|
554
|
+
: rawMessage.content);
|
|
549
555
|
const isError =
|
|
550
556
|
typeof rawMessage.error === "string" ||
|
|
551
557
|
rawMessage.isError === true ||
|
|
@@ -554,6 +560,9 @@ export function fromAgUiMessages(
|
|
|
554
560
|
: rawMessage.isError === false
|
|
555
561
|
? false
|
|
556
562
|
: undefined;
|
|
563
|
+
const mcpAppUri = readMcpAppResourceUri(mcpResult?._meta);
|
|
564
|
+
const mcpApp =
|
|
565
|
+
mcpAppUri !== undefined ? { resourceUri: mcpAppUri } : undefined;
|
|
557
566
|
|
|
558
567
|
let updated = false;
|
|
559
568
|
for (
|
|
@@ -581,10 +590,12 @@ export function fromAgUiMessages(
|
|
|
581
590
|
const updatedPart: ToolCallPart = {
|
|
582
591
|
...(part as ToolCallPart),
|
|
583
592
|
result,
|
|
593
|
+
...(mcpModelContent ? { modelContent: mcpModelContent } : {}),
|
|
584
594
|
...(isError !== undefined ? { isError } : {}),
|
|
585
595
|
...(toolMessageId !== undefined
|
|
586
596
|
? { unstable_toolMessageId: toolMessageId }
|
|
587
597
|
: {}),
|
|
598
|
+
...(mcpApp ? { mcp: { app: mcpApp } } : {}),
|
|
588
599
|
};
|
|
589
600
|
const updatedContent = message.content.map((contentPart, index) =>
|
|
590
601
|
index === partIndex ? updatedPart : contentPart,
|
|
@@ -615,10 +626,12 @@ export function fromAgUiMessages(
|
|
|
615
626
|
args: {},
|
|
616
627
|
argsText: "{}",
|
|
617
628
|
result,
|
|
629
|
+
...(mcpModelContent ? { modelContent: mcpModelContent } : {}),
|
|
618
630
|
...(isError !== undefined ? { isError } : {}),
|
|
619
631
|
...(toolMessageId !== undefined
|
|
620
632
|
? { unstable_toolMessageId: toolMessageId }
|
|
621
633
|
: {}),
|
|
634
|
+
...(mcpApp ? { mcp: { app: mcpApp } } : {}),
|
|
622
635
|
},
|
|
623
636
|
],
|
|
624
637
|
});
|
|
@@ -719,10 +732,9 @@ function convertAssistantMessage(
|
|
|
719
732
|
for (const { id: toolCallId, part } of toolCalls) {
|
|
720
733
|
if (part.result === undefined) continue;
|
|
721
734
|
|
|
722
|
-
const modelText = extractText(part.modelContent);
|
|
723
735
|
const resultContent =
|
|
724
|
-
|
|
725
|
-
?
|
|
736
|
+
part.modelContent !== undefined
|
|
737
|
+
? extractText(part.modelContent)
|
|
726
738
|
: typeof part.result === "string"
|
|
727
739
|
? part.result
|
|
728
740
|
: JSON.stringify(part.result);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ToolCallMessagePart,
|
|
9
9
|
type ToolModelContentPart,
|
|
10
10
|
} from "@assistant-ui/core";
|
|
11
|
+
import { readMcpAppResourceUri } from "../mcp-tool-result";
|
|
11
12
|
import type { AgUiEvent, AgUiInterrupt } from "../types";
|
|
12
13
|
import type { Logger } from "../logger";
|
|
13
14
|
|
|
@@ -34,9 +35,11 @@ type ToolCallState = {
|
|
|
34
35
|
snapshotResultApplied: boolean;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
|
|
38
|
+
export const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
|
|
38
39
|
|
|
39
|
-
const isPlainObject = (
|
|
40
|
+
export const isPlainObject = (
|
|
41
|
+
value: unknown,
|
|
42
|
+
): value is Record<string, unknown> =>
|
|
40
43
|
!!value && typeof value === "object" && !Array.isArray(value);
|
|
41
44
|
|
|
42
45
|
export type RunAggregatorOptions = {
|
|
@@ -235,8 +238,13 @@ export class RunAggregator {
|
|
|
235
238
|
this.finishToolCall(
|
|
236
239
|
event.toolCallId,
|
|
237
240
|
event.content ?? "",
|
|
238
|
-
event.
|
|
241
|
+
typeof event.mcpResult?.isError === "boolean"
|
|
242
|
+
? event.mcpResult.isError
|
|
243
|
+
: event.role === "tool"
|
|
244
|
+
? false
|
|
245
|
+
: undefined,
|
|
239
246
|
event.messageId,
|
|
247
|
+
event.mcpResult,
|
|
240
248
|
);
|
|
241
249
|
this.emit();
|
|
242
250
|
break;
|
|
@@ -442,6 +450,7 @@ export class RunAggregator {
|
|
|
442
450
|
content: string,
|
|
443
451
|
isError?: boolean,
|
|
444
452
|
toolMessageId?: string,
|
|
453
|
+
mcpResult?: Record<string, unknown>,
|
|
445
454
|
) {
|
|
446
455
|
if (!id) return;
|
|
447
456
|
let entry = this.toolCalls.get(id);
|
|
@@ -464,7 +473,16 @@ export class RunAggregator {
|
|
|
464
473
|
) {
|
|
465
474
|
this.partOrder.push({ kind: "tool-call", toolCallId: id });
|
|
466
475
|
}
|
|
467
|
-
if (entry.snapshotResultApplied) {
|
|
476
|
+
if (mcpResult !== undefined && !entry.snapshotResultApplied) {
|
|
477
|
+
entry.result = mcpResult;
|
|
478
|
+
entry.modelContent = [{ type: "text", text: content }];
|
|
479
|
+
entry.snapshotResultApplied = true;
|
|
480
|
+
entry.isError = isError;
|
|
481
|
+
if (entry.mcpAppResourceUri === undefined) {
|
|
482
|
+
const uri = readMcpAppResourceUri(mcpResult._meta);
|
|
483
|
+
if (uri !== undefined) entry.mcpAppResourceUri = uri;
|
|
484
|
+
}
|
|
485
|
+
} else if (entry.snapshotResultApplied) {
|
|
468
486
|
if (entry.modelContent === undefined) {
|
|
469
487
|
entry.modelContent = [{ type: "text", text: content }];
|
|
470
488
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgUiEvent, AgUiInterrupt, AgUiRunFinishedOutcome } from "./types";
|
|
2
2
|
import type { Logger } from "./logger";
|
|
3
|
+
import { parseMcpToolCallResult } from "./mcp-tool-result";
|
|
3
4
|
|
|
4
5
|
export type ParseAgUiEventOptions = {
|
|
5
6
|
logger?: Logger;
|
|
@@ -209,15 +210,17 @@ export const parseAgUiEvent = (
|
|
|
209
210
|
case "TOOL_CALL_RESULT": {
|
|
210
211
|
const toolCallId = getString("toolCallId");
|
|
211
212
|
if (!toolCallId) return null;
|
|
213
|
+
const content = getString("content") ?? "";
|
|
212
214
|
return withOptional(
|
|
213
215
|
{
|
|
214
216
|
type: "TOOL_CALL_RESULT" as const,
|
|
215
217
|
toolCallId,
|
|
216
|
-
content
|
|
218
|
+
content,
|
|
217
219
|
},
|
|
218
220
|
{
|
|
219
221
|
messageId: getString("messageId"),
|
|
220
222
|
role: payload.role === "tool" ? "tool" : undefined,
|
|
223
|
+
mcpResult: parseMcpToolCallResult(payload, content),
|
|
221
224
|
},
|
|
222
225
|
);
|
|
223
226
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isRecord } from "@assistant-ui/core/internal";
|
|
2
|
+
import { isMcpAppUri } from "@assistant-ui/core";
|
|
3
|
+
|
|
4
|
+
export type McpToolCallResult = Record<string, unknown> & {
|
|
5
|
+
content: unknown[];
|
|
6
|
+
structuredContent?: Record<string, unknown>;
|
|
7
|
+
_meta?: Record<string, unknown>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const parseMcpToolCallResult = (
|
|
12
|
+
source: Record<string, unknown>,
|
|
13
|
+
modelContent: string,
|
|
14
|
+
): McpToolCallResult | undefined => {
|
|
15
|
+
const structuredContent = isRecord(source.structuredContent)
|
|
16
|
+
? source.structuredContent
|
|
17
|
+
: undefined;
|
|
18
|
+
const meta = isRecord(source._meta) ? source._meta : undefined;
|
|
19
|
+
if (!structuredContent && !meta) return undefined;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
content: modelContent ? [{ type: "text", text: modelContent }] : [],
|
|
23
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
24
|
+
...(meta ? { _meta: meta } : {}),
|
|
25
|
+
...(typeof source.isError === "boolean" ? { isError: source.isError } : {}),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
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).
|
|
33
|
+
export const readMcpAppResourceUri = (meta: unknown): string | undefined => {
|
|
34
|
+
if (!isRecord(meta)) return undefined;
|
|
35
|
+
const uri = meta["ui/resourceUri"];
|
|
36
|
+
return typeof uri === "string" && isMcpAppUri(uri) ? uri : undefined;
|
|
37
|
+
};
|
package/src/runtime/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import type { AbstractAgent } from "@ag-ui/client";
|
|
14
14
|
import type { Logger } from "./logger";
|
|
15
15
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
16
|
+
import type { McpToolCallResult } from "./mcp-tool-result";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* @experimental This API is still under active development and might change without notice.
|
|
@@ -162,6 +163,7 @@ export type AgUiEvent =
|
|
|
162
163
|
toolCallId: string;
|
|
163
164
|
content: string;
|
|
164
165
|
role?: "tool";
|
|
166
|
+
mcpResult?: McpToolCallResult;
|
|
165
167
|
}
|
|
166
168
|
| {
|
|
167
169
|
type: "ACTIVITY_SNAPSHOT";
|