@assistant-ui/react-ag-ui 0.0.45 → 0.0.46
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 +83 -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 +12 -13
- 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 +13 -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 +11 -0
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -0
- package/dist/runtime/mcp-tool-result.js +20 -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 +157 -39
- package/src/runtime/adapter/conversions.ts +18 -16
- package/src/runtime/adapter/run-aggregator.ts +17 -4
- package/src/runtime/event-parser.ts +4 -1
- package/src/runtime/mcp-tool-result.ts +26 -0
- package/src/runtime/types.ts +2 -0
|
@@ -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,
|
|
@@ -24,6 +28,8 @@ import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
|
24
28
|
import {
|
|
25
29
|
AG_UI_METADATA_NAMESPACE,
|
|
26
30
|
type AgUiCustomMetadata,
|
|
31
|
+
isPlainObject,
|
|
32
|
+
MCP_APPS_ACTIVITY_TYPE,
|
|
27
33
|
RunAggregator,
|
|
28
34
|
tryParseJSON,
|
|
29
35
|
} from "./adapter/run-aggregator";
|
|
@@ -425,25 +431,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
425
431
|
);
|
|
426
432
|
}
|
|
427
433
|
|
|
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
|
-
}
|
|
434
|
+
const responsesById = this.collectInterruptResponses(
|
|
435
|
+
"submitInterruptResponses",
|
|
436
|
+
pending.interrupts,
|
|
437
|
+
responses,
|
|
438
|
+
);
|
|
447
439
|
|
|
448
440
|
const openIds = pending.interrupts.map((i) => i.id);
|
|
449
441
|
const missing = openIds.filter((id) => !responsesById.has(id));
|
|
@@ -452,13 +444,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
452
444
|
`[agui] submitInterruptResponses: missing responses for open interrupts: ${missing.join(", ")}`,
|
|
453
445
|
);
|
|
454
446
|
}
|
|
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
447
|
|
|
463
448
|
const now = Date.now();
|
|
464
449
|
for (const interrupt of pending.interrupts) {
|
|
@@ -538,35 +523,51 @@ export class AgUiThreadRuntimeCore {
|
|
|
538
523
|
interrupts: readonly AgUiInterrupt[],
|
|
539
524
|
responses: readonly AgUiResumeEntry[] | undefined,
|
|
540
525
|
): AgUiResumeEntry[] {
|
|
541
|
-
const
|
|
542
|
-
|
|
526
|
+
const responsesById = this.collectInterruptResponses(
|
|
527
|
+
"steerAway",
|
|
528
|
+
interrupts,
|
|
529
|
+
responses ?? [],
|
|
530
|
+
);
|
|
531
|
+
return interrupts.map(
|
|
532
|
+
(interrupt) =>
|
|
533
|
+
responsesById.get(interrupt.id) ?? {
|
|
534
|
+
interruptId: interrupt.id,
|
|
535
|
+
status: "cancelled",
|
|
536
|
+
},
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private collectInterruptResponses(
|
|
541
|
+
method: "submitInterruptResponses" | "steerAway",
|
|
542
|
+
interrupts: readonly AgUiInterrupt[],
|
|
543
|
+
responses: readonly AgUiResumeEntry[],
|
|
544
|
+
): Map<string, AgUiResumeEntry> {
|
|
545
|
+
const known = new Set(interrupts.map((interrupt) => interrupt.id));
|
|
543
546
|
const responsesById = new Map<string, AgUiResumeEntry>();
|
|
544
|
-
for (const entry of responses
|
|
547
|
+
for (const entry of responses) {
|
|
545
548
|
if (!entry || typeof entry.interruptId !== "string") {
|
|
546
549
|
throw new Error(
|
|
547
|
-
|
|
550
|
+
`[agui] ${method}: every entry must have an interruptId`,
|
|
548
551
|
);
|
|
549
552
|
}
|
|
550
553
|
if (entry.status !== "resolved" && entry.status !== "cancelled") {
|
|
551
554
|
throw new Error(
|
|
552
|
-
`[agui]
|
|
555
|
+
`[agui] ${method}: invalid status "${entry.status}" for interrupt ${entry.interruptId}`,
|
|
553
556
|
);
|
|
554
557
|
}
|
|
555
558
|
if (!known.has(entry.interruptId)) {
|
|
556
559
|
throw new Error(
|
|
557
|
-
`[agui]
|
|
560
|
+
`[agui] ${method}: unknown interrupt id ${entry.interruptId}`,
|
|
558
561
|
);
|
|
559
562
|
}
|
|
560
563
|
if (responsesById.has(entry.interruptId)) {
|
|
561
564
|
throw new Error(
|
|
562
|
-
`[agui]
|
|
565
|
+
`[agui] ${method}: duplicate response for interrupt ${entry.interruptId}`,
|
|
563
566
|
);
|
|
564
567
|
}
|
|
565
568
|
responsesById.set(entry.interruptId, entry);
|
|
566
569
|
}
|
|
567
|
-
return
|
|
568
|
-
(id) => responsesById.get(id) ?? { interruptId: id, status: "cancelled" },
|
|
569
|
-
);
|
|
570
|
+
return responsesById;
|
|
570
571
|
}
|
|
571
572
|
|
|
572
573
|
private toAppendMessage(message: CreateAppendMessage): AppendMessage {
|
|
@@ -1344,6 +1345,22 @@ export class AgUiThreadRuntimeCore {
|
|
|
1344
1345
|
aggregator.handle(event);
|
|
1345
1346
|
return;
|
|
1346
1347
|
}
|
|
1348
|
+
case "ACTIVITY_SNAPSHOT": {
|
|
1349
|
+
const toolCallId = event.content["toolCallId"];
|
|
1350
|
+
if (
|
|
1351
|
+
event.activityType === MCP_APPS_ACTIVITY_TYPE &&
|
|
1352
|
+
typeof toolCallId === "string" &&
|
|
1353
|
+
!aggregator.hasToolCall(toolCallId)
|
|
1354
|
+
) {
|
|
1355
|
+
const messageId = this.findMessageIdForToolCall(toolCallId);
|
|
1356
|
+
if (messageId !== undefined) {
|
|
1357
|
+
this.applyCrossRunActivitySnapshot(messageId, toolCallId, event);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
aggregator.handle(event);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1347
1364
|
default:
|
|
1348
1365
|
aggregator.handle(event);
|
|
1349
1366
|
}
|
|
@@ -1361,10 +1378,49 @@ export class AgUiThreadRuntimeCore {
|
|
|
1361
1378
|
if (part.type !== "tool-call" || part.toolCallId !== event.toolCallId)
|
|
1362
1379
|
return part;
|
|
1363
1380
|
matchedToolCall = true;
|
|
1381
|
+
// An applied activity snapshot owns part.result; a later result only
|
|
1382
|
+
// fills what is missing, mirroring the aggregator's finishToolCall.
|
|
1383
|
+
// The aggregator's flag is set only when the snapshot carried a
|
|
1384
|
+
// result, so app presence alone is not enough: the current result
|
|
1385
|
+
// must be CallToolResult-shaped (a required content array).
|
|
1386
|
+
const snapshotResultApplied =
|
|
1387
|
+
part.mcp?.app !== undefined &&
|
|
1388
|
+
isPlainObject(part.result) &&
|
|
1389
|
+
Array.isArray((part.result as Record<string, unknown>)["content"]);
|
|
1390
|
+
if (snapshotResultApplied) {
|
|
1391
|
+
return {
|
|
1392
|
+
...part,
|
|
1393
|
+
...(part.modelContent === undefined && event.content
|
|
1394
|
+
? {
|
|
1395
|
+
modelContent: [
|
|
1396
|
+
{ type: "text" as const, text: event.content },
|
|
1397
|
+
],
|
|
1398
|
+
}
|
|
1399
|
+
: {}),
|
|
1400
|
+
...(part.isError === undefined
|
|
1401
|
+
? typeof event.mcpResult?.isError === "boolean"
|
|
1402
|
+
? { isError: event.mcpResult.isError }
|
|
1403
|
+
: event.role === "tool"
|
|
1404
|
+
? { isError: false }
|
|
1405
|
+
: {}
|
|
1406
|
+
: {}),
|
|
1407
|
+
...(event.messageId
|
|
1408
|
+
? { unstable_toolMessageId: event.messageId }
|
|
1409
|
+
: {}),
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1364
1412
|
return {
|
|
1365
1413
|
...part,
|
|
1366
|
-
result:
|
|
1367
|
-
|
|
1414
|
+
result: (event.mcpResult ??
|
|
1415
|
+
tryParseJSON(event.content ?? "")) as ReadonlyJSONValue,
|
|
1416
|
+
...(event.mcpResult !== undefined
|
|
1417
|
+
? { modelContent: [{ type: "text" as const, text: event.content }] }
|
|
1418
|
+
: {}),
|
|
1419
|
+
...(typeof event.mcpResult?.isError === "boolean"
|
|
1420
|
+
? { isError: event.mcpResult.isError }
|
|
1421
|
+
: event.role === "tool"
|
|
1422
|
+
? { isError: false }
|
|
1423
|
+
: {}),
|
|
1368
1424
|
...(event.messageId
|
|
1369
1425
|
? { unstable_toolMessageId: event.messageId }
|
|
1370
1426
|
: {}),
|
|
@@ -1381,6 +1437,68 @@ export class AgUiThreadRuntimeCore {
|
|
|
1381
1437
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1382
1438
|
}
|
|
1383
1439
|
|
|
1440
|
+
private applyCrossRunActivitySnapshot(
|
|
1441
|
+
messageId: string,
|
|
1442
|
+
toolCallId: string,
|
|
1443
|
+
event: Extract<AgUiEvent, { type: "ACTIVITY_SNAPSHOT" }>,
|
|
1444
|
+
): void {
|
|
1445
|
+
const resourceUri = event.content["resourceUri"];
|
|
1446
|
+
if (typeof resourceUri !== "string" || !isMcpAppUri(resourceUri)) return;
|
|
1447
|
+
const serverId = event.content["serverId"];
|
|
1448
|
+
const serverHash = event.content["serverHash"];
|
|
1449
|
+
const appServerId =
|
|
1450
|
+
typeof serverId === "string" && serverId.length > 0
|
|
1451
|
+
? serverId
|
|
1452
|
+
: typeof serverHash === "string" && serverHash.length > 0
|
|
1453
|
+
? serverHash
|
|
1454
|
+
: undefined;
|
|
1455
|
+
const result = event.content["result"];
|
|
1456
|
+
const updated = this.updateMessage(messageId, (message) => {
|
|
1457
|
+
if (message.role !== "assistant") return message;
|
|
1458
|
+
const assistant = message as ThreadAssistantMessage;
|
|
1459
|
+
let matchedToolCall = false;
|
|
1460
|
+
const content = assistant.content.map((part) => {
|
|
1461
|
+
if (part.type !== "tool-call" || part.toolCallId !== toolCallId)
|
|
1462
|
+
return part;
|
|
1463
|
+
matchedToolCall = true;
|
|
1464
|
+
return {
|
|
1465
|
+
...part,
|
|
1466
|
+
mcp: {
|
|
1467
|
+
app: {
|
|
1468
|
+
resourceUri,
|
|
1469
|
+
...(appServerId ? { serverId: appServerId } : {}),
|
|
1470
|
+
},
|
|
1471
|
+
},
|
|
1472
|
+
...(isPlainObject(result)
|
|
1473
|
+
? {
|
|
1474
|
+
...(part.result !== undefined && part.modelContent === undefined
|
|
1475
|
+
? {
|
|
1476
|
+
modelContent: [
|
|
1477
|
+
{
|
|
1478
|
+
type: "text" as const,
|
|
1479
|
+
text:
|
|
1480
|
+
typeof part.result === "string"
|
|
1481
|
+
? part.result
|
|
1482
|
+
: JSON.stringify(part.result),
|
|
1483
|
+
},
|
|
1484
|
+
],
|
|
1485
|
+
}
|
|
1486
|
+
: {}),
|
|
1487
|
+
result: result as ReadonlyJSONValue,
|
|
1488
|
+
isError: result["isError"] === true,
|
|
1489
|
+
}
|
|
1490
|
+
: {}),
|
|
1491
|
+
};
|
|
1492
|
+
});
|
|
1493
|
+
if (!matchedToolCall) return message;
|
|
1494
|
+
return { ...assistant, content };
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
if (!updated) return;
|
|
1498
|
+
this.notifyUpdate();
|
|
1499
|
+
this.maybeCompleteAfterToolResults(messageId);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1384
1502
|
private importMessagesSnapshot(rawMessages: readonly unknown[]) {
|
|
1385
1503
|
try {
|
|
1386
1504
|
const normalized = fromAgUiMessages(rawMessages, {
|
|
@@ -5,7 +5,11 @@ import type {
|
|
|
5
5
|
ThreadMessageLike as CoreThreadMessageLike,
|
|
6
6
|
ToolModelContentPart,
|
|
7
7
|
} from "@assistant-ui/core";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getAutoStatus,
|
|
10
|
+
httpUrlPattern,
|
|
11
|
+
parseDataUrl,
|
|
12
|
+
} from "@assistant-ui/core/internal";
|
|
9
13
|
import { type Tool, toToolsJSONSchema } from "assistant-stream";
|
|
10
14
|
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
11
15
|
import {
|
|
@@ -13,6 +17,7 @@ import {
|
|
|
13
17
|
type AgUiCustomMetadata,
|
|
14
18
|
} from "./run-aggregator";
|
|
15
19
|
import type { AgUiInterrupt } from "../types";
|
|
20
|
+
import { parseMcpToolCallResult } from "../mcp-tool-result";
|
|
16
21
|
|
|
17
22
|
export type { InputContent };
|
|
18
23
|
|
|
@@ -129,16 +134,6 @@ function extractText(content: unknown): string {
|
|
|
129
134
|
.join("\n");
|
|
130
135
|
}
|
|
131
136
|
|
|
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
137
|
type InputContentSource =
|
|
143
138
|
| { type: "data"; value: string; mimeType: string }
|
|
144
139
|
| { type: "url"; value: string; mimeType?: string };
|
|
@@ -540,12 +535,18 @@ export function fromAgUiMessages(
|
|
|
540
535
|
if (role === "tool") {
|
|
541
536
|
const toolCallId = getToolCallId(rawMessage) ?? `tool-${generateId()}`;
|
|
542
537
|
const toolMessageId = getString(rawMessage, "id");
|
|
538
|
+
const modelContent = extractText(rawMessage.content);
|
|
539
|
+
const mcpResult = parseMcpToolCallResult(rawMessage, modelContent);
|
|
540
|
+
const mcpModelContent = mcpResult
|
|
541
|
+
? [{ type: "text" as const, text: modelContent }]
|
|
542
|
+
: undefined;
|
|
543
543
|
const result =
|
|
544
|
-
|
|
544
|
+
mcpResult ??
|
|
545
|
+
(rawMessage.result !== undefined
|
|
545
546
|
? rawMessage.result
|
|
546
547
|
: typeof rawMessage.content === "string"
|
|
547
548
|
? parseJSONText(rawMessage.content)
|
|
548
|
-
: rawMessage.content;
|
|
549
|
+
: rawMessage.content);
|
|
549
550
|
const isError =
|
|
550
551
|
typeof rawMessage.error === "string" ||
|
|
551
552
|
rawMessage.isError === true ||
|
|
@@ -581,6 +582,7 @@ export function fromAgUiMessages(
|
|
|
581
582
|
const updatedPart: ToolCallPart = {
|
|
582
583
|
...(part as ToolCallPart),
|
|
583
584
|
result,
|
|
585
|
+
...(mcpModelContent ? { modelContent: mcpModelContent } : {}),
|
|
584
586
|
...(isError !== undefined ? { isError } : {}),
|
|
585
587
|
...(toolMessageId !== undefined
|
|
586
588
|
? { unstable_toolMessageId: toolMessageId }
|
|
@@ -615,6 +617,7 @@ export function fromAgUiMessages(
|
|
|
615
617
|
args: {},
|
|
616
618
|
argsText: "{}",
|
|
617
619
|
result,
|
|
620
|
+
...(mcpModelContent ? { modelContent: mcpModelContent } : {}),
|
|
618
621
|
...(isError !== undefined ? { isError } : {}),
|
|
619
622
|
...(toolMessageId !== undefined
|
|
620
623
|
? { unstable_toolMessageId: toolMessageId }
|
|
@@ -719,10 +722,9 @@ function convertAssistantMessage(
|
|
|
719
722
|
for (const { id: toolCallId, part } of toolCalls) {
|
|
720
723
|
if (part.result === undefined) continue;
|
|
721
724
|
|
|
722
|
-
const modelText = extractText(part.modelContent);
|
|
723
725
|
const resultContent =
|
|
724
|
-
|
|
725
|
-
?
|
|
726
|
+
part.modelContent !== undefined
|
|
727
|
+
? extractText(part.modelContent)
|
|
726
728
|
: typeof part.result === "string"
|
|
727
729
|
? part.result
|
|
728
730
|
: JSON.stringify(part.result);
|
|
@@ -34,9 +34,11 @@ type ToolCallState = {
|
|
|
34
34
|
snapshotResultApplied: boolean;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
|
|
37
|
+
export const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
|
|
38
38
|
|
|
39
|
-
const isPlainObject = (
|
|
39
|
+
export const isPlainObject = (
|
|
40
|
+
value: unknown,
|
|
41
|
+
): value is Record<string, unknown> =>
|
|
40
42
|
!!value && typeof value === "object" && !Array.isArray(value);
|
|
41
43
|
|
|
42
44
|
export type RunAggregatorOptions = {
|
|
@@ -235,8 +237,13 @@ export class RunAggregator {
|
|
|
235
237
|
this.finishToolCall(
|
|
236
238
|
event.toolCallId,
|
|
237
239
|
event.content ?? "",
|
|
238
|
-
event.
|
|
240
|
+
typeof event.mcpResult?.isError === "boolean"
|
|
241
|
+
? event.mcpResult.isError
|
|
242
|
+
: event.role === "tool"
|
|
243
|
+
? false
|
|
244
|
+
: undefined,
|
|
239
245
|
event.messageId,
|
|
246
|
+
event.mcpResult,
|
|
240
247
|
);
|
|
241
248
|
this.emit();
|
|
242
249
|
break;
|
|
@@ -442,6 +449,7 @@ export class RunAggregator {
|
|
|
442
449
|
content: string,
|
|
443
450
|
isError?: boolean,
|
|
444
451
|
toolMessageId?: string,
|
|
452
|
+
mcpResult?: Record<string, unknown>,
|
|
445
453
|
) {
|
|
446
454
|
if (!id) return;
|
|
447
455
|
let entry = this.toolCalls.get(id);
|
|
@@ -464,7 +472,12 @@ export class RunAggregator {
|
|
|
464
472
|
) {
|
|
465
473
|
this.partOrder.push({ kind: "tool-call", toolCallId: id });
|
|
466
474
|
}
|
|
467
|
-
if (entry.snapshotResultApplied) {
|
|
475
|
+
if (mcpResult !== undefined && !entry.snapshotResultApplied) {
|
|
476
|
+
entry.result = mcpResult;
|
|
477
|
+
entry.modelContent = [{ type: "text", text: content }];
|
|
478
|
+
entry.snapshotResultApplied = true;
|
|
479
|
+
entry.isError = isError;
|
|
480
|
+
} else if (entry.snapshotResultApplied) {
|
|
468
481
|
if (entry.modelContent === undefined) {
|
|
469
482
|
entry.modelContent = [{ type: "text", text: content }];
|
|
470
483
|
}
|
|
@@ -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,26 @@
|
|
|
1
|
+
import { isRecord } from "@assistant-ui/core/internal";
|
|
2
|
+
|
|
3
|
+
export type McpToolCallResult = Record<string, unknown> & {
|
|
4
|
+
content: unknown[];
|
|
5
|
+
structuredContent?: Record<string, unknown>;
|
|
6
|
+
_meta?: Record<string, unknown>;
|
|
7
|
+
isError?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const parseMcpToolCallResult = (
|
|
11
|
+
source: Record<string, unknown>,
|
|
12
|
+
modelContent: string,
|
|
13
|
+
): McpToolCallResult | undefined => {
|
|
14
|
+
const structuredContent = isRecord(source.structuredContent)
|
|
15
|
+
? source.structuredContent
|
|
16
|
+
: undefined;
|
|
17
|
+
const meta = isRecord(source._meta) ? source._meta : undefined;
|
|
18
|
+
if (!structuredContent && !meta) return undefined;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
content: modelContent ? [{ type: "text", text: modelContent }] : [],
|
|
22
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
23
|
+
...(meta ? { _meta: meta } : {}),
|
|
24
|
+
...(typeof source.isError === "boolean" ? { isError: source.isError } : {}),
|
|
25
|
+
};
|
|
26
|
+
};
|
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";
|