@assistant-ui/react-ag-ui 0.0.68 → 0.0.70
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 +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +4 -3
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +20 -6
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +5 -0
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +104 -15
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +1 -1
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/types.d.ts +16 -2
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +5 -3
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +6 -5
- package/src/runtime/AgUiThreadRuntimeCore.interrupt-cancel.test.ts +202 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +25 -7
- package/src/runtime/adapter/conversions.metadata.test.ts +400 -0
- package/src/runtime/adapter/conversions.ts +170 -28
- package/src/runtime/adapter/run-aggregator.approval.test.ts +4 -4
- package/src/runtime/adapter/run-aggregator.ts +8 -2
- package/src/runtime/types.ts +18 -1
- package/src/useAgUiRuntime.context.test.tsx +227 -0
- package/src/useAgUiRuntime.ssr.test.tsx +33 -0
- package/src/useAgUiRuntime.ts +7 -3
|
@@ -69,6 +69,7 @@ export type AgUiMessage =
|
|
|
69
69
|
role: "user";
|
|
70
70
|
content: string | InputContent[];
|
|
71
71
|
name?: string;
|
|
72
|
+
metadata?: Record<string, unknown>;
|
|
72
73
|
}
|
|
73
74
|
| {
|
|
74
75
|
id: string;
|
|
@@ -76,18 +77,21 @@ export type AgUiMessage =
|
|
|
76
77
|
content: string;
|
|
77
78
|
name?: string;
|
|
78
79
|
toolCalls?: AgUiToolCall[];
|
|
80
|
+
metadata?: Record<string, unknown>;
|
|
79
81
|
}
|
|
80
82
|
| {
|
|
81
83
|
id: string;
|
|
82
84
|
role: "system" | "developer";
|
|
83
85
|
content: string;
|
|
84
86
|
name?: string;
|
|
87
|
+
metadata?: Record<string, unknown>;
|
|
85
88
|
}
|
|
86
89
|
| {
|
|
87
90
|
id: string;
|
|
88
91
|
role: "reasoning";
|
|
89
92
|
content: string;
|
|
90
93
|
encryptedValue?: string;
|
|
94
|
+
metadata?: Record<string, unknown>;
|
|
91
95
|
}
|
|
92
96
|
| {
|
|
93
97
|
id: string;
|
|
@@ -95,6 +99,7 @@ export type AgUiMessage =
|
|
|
95
99
|
content: string;
|
|
96
100
|
toolCallId: string;
|
|
97
101
|
error?: string;
|
|
102
|
+
metadata?: Record<string, unknown>;
|
|
98
103
|
};
|
|
99
104
|
|
|
100
105
|
type CoreToolCallPart = Extract<
|
|
@@ -117,6 +122,108 @@ const getString = (record: Record<string, unknown>, key: string) => {
|
|
|
117
122
|
return typeof value === "string" ? value : undefined;
|
|
118
123
|
};
|
|
119
124
|
|
|
125
|
+
function toAgUiWireMetadata(
|
|
126
|
+
metadata: unknown,
|
|
127
|
+
): Record<string, unknown> | undefined {
|
|
128
|
+
return isObject(metadata) ? { ...metadata } : undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readLegacyAgUiNamespace(metadata: unknown): Record<string, unknown> {
|
|
132
|
+
if (!isObject(metadata) || !isObject(metadata.custom)) return {};
|
|
133
|
+
const namespaced = metadata.custom[AG_UI_METADATA_NAMESPACE];
|
|
134
|
+
if (!isObject(namespaced)) return {};
|
|
135
|
+
return "interrupts" in namespaced
|
|
136
|
+
? { interrupts: namespaced.interrupts }
|
|
137
|
+
: {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function withoutPersistedInterrupts(
|
|
141
|
+
wire: Record<string, unknown>,
|
|
142
|
+
): Record<string, unknown> {
|
|
143
|
+
if (!isObject(wire.custom)) return wire;
|
|
144
|
+
const namespaced = wire.custom[AG_UI_METADATA_NAMESPACE];
|
|
145
|
+
if (!isObject(namespaced) || !("interrupts" in namespaced)) return wire;
|
|
146
|
+
|
|
147
|
+
const { interrupts: _interrupts, ...remainingNamespace } = namespaced;
|
|
148
|
+
const { [AG_UI_METADATA_NAMESPACE]: _namespace, ...remainingCustom } =
|
|
149
|
+
wire.custom;
|
|
150
|
+
if (Object.keys(remainingNamespace).length > 0) {
|
|
151
|
+
remainingCustom[AG_UI_METADATA_NAMESPACE] = remainingNamespace;
|
|
152
|
+
}
|
|
153
|
+
const { custom: _custom, ...remainingWire } = wire;
|
|
154
|
+
return Object.keys(remainingCustom).length > 0
|
|
155
|
+
? { ...remainingWire, custom: remainingCustom }
|
|
156
|
+
: remainingWire;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function toSnapshotCustom(
|
|
160
|
+
metadata: unknown,
|
|
161
|
+
state: Record<string, unknown> = {},
|
|
162
|
+
): Record<string, unknown> {
|
|
163
|
+
const wire = withoutPersistedInterrupts(toAgUiWireMetadata(metadata) ?? {});
|
|
164
|
+
const needsWireSlot =
|
|
165
|
+
isObject(metadata) &&
|
|
166
|
+
(Object.keys(metadata).length === 0 || AG_UI_METADATA_NAMESPACE in wire);
|
|
167
|
+
const namespaced = {
|
|
168
|
+
...readLegacyAgUiNamespace(metadata),
|
|
169
|
+
...state,
|
|
170
|
+
...(needsWireSlot ? { messageMetadata: wire } : {}),
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
...wire,
|
|
174
|
+
...(Object.keys(namespaced).length > 0
|
|
175
|
+
? { [AG_UI_METADATA_NAMESPACE]: namespaced }
|
|
176
|
+
: {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function toAgUiMessageMetadata(
|
|
181
|
+
metadata: unknown,
|
|
182
|
+
): Record<string, unknown> | undefined {
|
|
183
|
+
const custom = readCustomMetadata(metadata);
|
|
184
|
+
const namespaced = readNamespacedMetadata(metadata);
|
|
185
|
+
if (isObject(namespaced.messageMetadata)) {
|
|
186
|
+
return { ...namespaced.messageMetadata };
|
|
187
|
+
}
|
|
188
|
+
const { [AG_UI_METADATA_NAMESPACE]: _state, ...wire } = custom;
|
|
189
|
+
return Object.keys(wire).length > 0 ? wire : undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function withAgUiMessageMetadata(metadata: unknown): {
|
|
193
|
+
metadata?: Record<string, unknown>;
|
|
194
|
+
} {
|
|
195
|
+
const messageMetadata = toAgUiMessageMetadata(metadata);
|
|
196
|
+
return messageMetadata !== undefined ? { metadata: messageMetadata } : {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function withAgUiPartMessageMetadata(
|
|
200
|
+
providerMetadata: PartProviderMetadata | undefined,
|
|
201
|
+
messageMetadata: Record<string, unknown> | undefined,
|
|
202
|
+
): PartProviderMetadata {
|
|
203
|
+
if (!messageMetadata) return providerMetadata ?? {};
|
|
204
|
+
|
|
205
|
+
const existing = providerMetadata ?? {};
|
|
206
|
+
const namespaced = isObject(existing[AG_UI_METADATA_NAMESPACE])
|
|
207
|
+
? existing[AG_UI_METADATA_NAMESPACE]
|
|
208
|
+
: {};
|
|
209
|
+
return {
|
|
210
|
+
...existing,
|
|
211
|
+
[AG_UI_METADATA_NAMESPACE]: {
|
|
212
|
+
...namespaced,
|
|
213
|
+
messageMetadata: messageMetadata as ReadonlyJSONObject,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function readAgUiPartMessageMetadata(
|
|
219
|
+
providerMetadata: unknown,
|
|
220
|
+
): Record<string, unknown> | undefined {
|
|
221
|
+
const namespaced = readAgUiNamespace(providerMetadata);
|
|
222
|
+
return namespaced && isObject(namespaced.messageMetadata)
|
|
223
|
+
? toAgUiWireMetadata(namespaced.messageMetadata)
|
|
224
|
+
: undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
120
227
|
const getToolCallId = (record: Record<string, unknown>) =>
|
|
121
228
|
getString(record, "toolCallId") ?? getString(record, "tool_call_id");
|
|
122
229
|
|
|
@@ -132,6 +239,7 @@ const readAgUiReasoningMeta = (providerMetadata: unknown) => {
|
|
|
132
239
|
return {
|
|
133
240
|
encryptedValue: getString(namespaced, "encryptedValue"),
|
|
134
241
|
reasoningId: getString(namespaced, "reasoningId"),
|
|
242
|
+
messageMetadata: toAgUiWireMetadata(namespaced.messageMetadata),
|
|
135
243
|
};
|
|
136
244
|
};
|
|
137
245
|
|
|
@@ -600,8 +708,14 @@ function readOpaqueReasoning(metadata: unknown): AgUiOpaqueReasoning[] {
|
|
|
600
708
|
const id = getString(entry, "id");
|
|
601
709
|
const encryptedValue = getString(entry, "encryptedValue");
|
|
602
710
|
if (!id?.trim() || !encryptedValue?.trim()) return [];
|
|
711
|
+
const metadata = toAgUiWireMetadata(entry.metadata);
|
|
603
712
|
return [
|
|
604
|
-
{
|
|
713
|
+
{
|
|
714
|
+
id,
|
|
715
|
+
encryptedValue,
|
|
716
|
+
...(metadata ? { metadata } : {}),
|
|
717
|
+
...(entry.after === true ? { after: true } : {}),
|
|
718
|
+
},
|
|
605
719
|
];
|
|
606
720
|
});
|
|
607
721
|
}
|
|
@@ -631,7 +745,8 @@ function toAssistantSnapshotMessage(
|
|
|
631
745
|
rawMessage: Record<string, unknown>,
|
|
632
746
|
): CoreThreadMessageLike {
|
|
633
747
|
const text = extractText(rawMessage.content);
|
|
634
|
-
const
|
|
748
|
+
const custom = toSnapshotCustom(rawMessage.metadata);
|
|
749
|
+
const interrupts = readPersistedInterrupts({ custom });
|
|
635
750
|
const restoredToolCalls = extractAssistantToolCalls(rawMessage);
|
|
636
751
|
const approvals = projectAgUiToolApprovals(
|
|
637
752
|
interrupts,
|
|
@@ -652,21 +767,24 @@ function toAssistantSnapshotMessage(
|
|
|
652
767
|
...toolCallParts,
|
|
653
768
|
];
|
|
654
769
|
const messageName = getString(rawMessage, "name");
|
|
770
|
+
const namespaced = isObject(custom[AG_UI_METADATA_NAMESPACE])
|
|
771
|
+
? { ...custom[AG_UI_METADATA_NAMESPACE] }
|
|
772
|
+
: {};
|
|
773
|
+
if (interrupts) namespaced.interrupts = interrupts;
|
|
774
|
+
else delete namespaced.interrupts;
|
|
775
|
+
const messageCustom = { ...custom };
|
|
776
|
+
if (Object.keys(namespaced).length > 0) {
|
|
777
|
+
messageCustom[AG_UI_METADATA_NAMESPACE] = namespaced;
|
|
778
|
+
} else {
|
|
779
|
+
delete messageCustom[AG_UI_METADATA_NAMESPACE];
|
|
780
|
+
}
|
|
655
781
|
return {
|
|
656
782
|
id: getString(rawMessage, "id") ?? generateId(),
|
|
657
783
|
role: "assistant",
|
|
658
784
|
content: assistantContent.length > 0 ? assistantContent : "",
|
|
659
785
|
...(messageName !== undefined ? { name: messageName } : {}),
|
|
660
|
-
...(
|
|
661
|
-
? {
|
|
662
|
-
metadata: {
|
|
663
|
-
custom: {
|
|
664
|
-
[AG_UI_METADATA_NAMESPACE]: {
|
|
665
|
-
interrupts,
|
|
666
|
-
} satisfies AgUiCustomMetadata,
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
}
|
|
786
|
+
...(Object.keys(messageCustom).length > 0
|
|
787
|
+
? { metadata: { custom: messageCustom } }
|
|
670
788
|
: {}),
|
|
671
789
|
};
|
|
672
790
|
}
|
|
@@ -676,6 +794,10 @@ function toUserOrSystemSnapshotMessage(
|
|
|
676
794
|
rawMessage: Record<string, unknown>,
|
|
677
795
|
): CoreThreadMessageLike {
|
|
678
796
|
const messageName = getString(rawMessage, "name");
|
|
797
|
+
const custom = toSnapshotCustom(
|
|
798
|
+
rawMessage.metadata,
|
|
799
|
+
role === "developer" ? { role: "developer" } : {},
|
|
800
|
+
);
|
|
679
801
|
const attachments =
|
|
680
802
|
role === "user" ? toSnapshotAttachments(rawMessage.content) : [];
|
|
681
803
|
return {
|
|
@@ -686,17 +808,7 @@ function toUserOrSystemSnapshotMessage(
|
|
|
686
808
|
content: extractText(rawMessage.content),
|
|
687
809
|
...(messageName !== undefined ? { name: messageName } : {}),
|
|
688
810
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
689
|
-
...(
|
|
690
|
-
? {
|
|
691
|
-
metadata: {
|
|
692
|
-
custom: {
|
|
693
|
-
[AG_UI_METADATA_NAMESPACE]: {
|
|
694
|
-
role: "developer",
|
|
695
|
-
} satisfies AgUiCustomMetadata,
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
}
|
|
699
|
-
: {}),
|
|
811
|
+
...(Object.keys(custom).length > 0 ? { metadata: { custom } } : {}),
|
|
700
812
|
};
|
|
701
813
|
}
|
|
702
814
|
|
|
@@ -941,6 +1053,7 @@ export function fromAgUiMessages(
|
|
|
941
1053
|
const mcpAppUri = readMcpAppResourceUri(mcpResult?._meta);
|
|
942
1054
|
const mcpApp =
|
|
943
1055
|
mcpAppUri !== undefined ? { resourceUri: mcpAppUri } : undefined;
|
|
1056
|
+
const messageMetadata = toAgUiWireMetadata(rawMessage.metadata);
|
|
944
1057
|
|
|
945
1058
|
let updated = false;
|
|
946
1059
|
for (
|
|
@@ -974,6 +1087,14 @@ export function fromAgUiMessages(
|
|
|
974
1087
|
? { unstable_toolMessageId: toolMessageId }
|
|
975
1088
|
: {}),
|
|
976
1089
|
...(mcpApp ? { mcp: { app: mcpApp } } : {}),
|
|
1090
|
+
...(messageMetadata
|
|
1091
|
+
? {
|
|
1092
|
+
providerMetadata: withAgUiPartMessageMetadata(
|
|
1093
|
+
(part as ToolCallPart).providerMetadata,
|
|
1094
|
+
messageMetadata,
|
|
1095
|
+
),
|
|
1096
|
+
}
|
|
1097
|
+
: {}),
|
|
977
1098
|
};
|
|
978
1099
|
const updatedContent = message.content.map((contentPart, index) =>
|
|
979
1100
|
index === partIndex ? updatedPart : contentPart,
|
|
@@ -1010,6 +1131,14 @@ export function fromAgUiMessages(
|
|
|
1010
1131
|
? { unstable_toolMessageId: toolMessageId }
|
|
1011
1132
|
: {}),
|
|
1012
1133
|
...(mcpApp ? { mcp: { app: mcpApp } } : {}),
|
|
1134
|
+
...(messageMetadata
|
|
1135
|
+
? {
|
|
1136
|
+
providerMetadata: withAgUiPartMessageMetadata(
|
|
1137
|
+
undefined,
|
|
1138
|
+
messageMetadata,
|
|
1139
|
+
),
|
|
1140
|
+
}
|
|
1141
|
+
: {}),
|
|
1013
1142
|
},
|
|
1014
1143
|
],
|
|
1015
1144
|
});
|
|
@@ -1108,6 +1237,7 @@ export function fromAgUiMessages(
|
|
|
1108
1237
|
if (role === "reasoning") {
|
|
1109
1238
|
const text = extractText(rawMessage.content);
|
|
1110
1239
|
const encryptedValue = getString(rawMessage, "encryptedValue");
|
|
1240
|
+
const messageMetadata = toAgUiWireMetadata(rawMessage.metadata);
|
|
1111
1241
|
if (text.trim().length === 0) {
|
|
1112
1242
|
// showThinking hides reasoning; this record is never rendered anyway,
|
|
1113
1243
|
// and dropping it would deny the provider the payload it needs to
|
|
@@ -1120,6 +1250,7 @@ export function fromAgUiMessages(
|
|
|
1120
1250
|
opaqueReasoning.push({
|
|
1121
1251
|
id: opaqueId,
|
|
1122
1252
|
encryptedValue,
|
|
1253
|
+
...(messageMetadata ? { metadata: messageMetadata } : {}),
|
|
1123
1254
|
anchor: converted.length,
|
|
1124
1255
|
});
|
|
1125
1256
|
}
|
|
@@ -1135,6 +1266,7 @@ export function fromAgUiMessages(
|
|
|
1135
1266
|
opaqueReasoning.push({
|
|
1136
1267
|
id: hiddenId,
|
|
1137
1268
|
encryptedValue,
|
|
1269
|
+
...(messageMetadata ? { metadata: messageMetadata } : {}),
|
|
1138
1270
|
anchor: converted.length,
|
|
1139
1271
|
});
|
|
1140
1272
|
}
|
|
@@ -1149,6 +1281,9 @@ export function fromAgUiMessages(
|
|
|
1149
1281
|
const meta = {
|
|
1150
1282
|
...(reasoningId !== undefined ? { reasoningId } : {}),
|
|
1151
1283
|
...(encryptedValue !== undefined ? { encryptedValue } : {}),
|
|
1284
|
+
...(messageMetadata
|
|
1285
|
+
? { messageMetadata: messageMetadata as ReadonlyJSONObject }
|
|
1286
|
+
: {}),
|
|
1152
1287
|
};
|
|
1153
1288
|
pendingReasoning.push({
|
|
1154
1289
|
id: reasoningId,
|
|
@@ -1265,9 +1400,8 @@ function convertAssistantMessage(
|
|
|
1265
1400
|
if (!isObject(part) || part.type !== "reasoning") continue;
|
|
1266
1401
|
const text = getString(part, "text") ?? "";
|
|
1267
1402
|
if (text.trim().length === 0) continue;
|
|
1268
|
-
const { encryptedValue, reasoningId } =
|
|
1269
|
-
part.providerMetadata
|
|
1270
|
-
);
|
|
1403
|
+
const { encryptedValue, reasoningId, messageMetadata } =
|
|
1404
|
+
readAgUiReasoningMeta(part.providerMetadata);
|
|
1271
1405
|
converted.push({
|
|
1272
1406
|
// The wire id is what a signature was issued against, so it wins over a
|
|
1273
1407
|
// synthesized one whenever the run carried it.
|
|
@@ -1279,12 +1413,12 @@ function convertAssistantMessage(
|
|
|
1279
1413
|
role: "reasoning",
|
|
1280
1414
|
content: text,
|
|
1281
1415
|
...(encryptedValue !== undefined ? { encryptedValue } : {}),
|
|
1416
|
+
...(messageMetadata ? { metadata: messageMetadata } : {}),
|
|
1282
1417
|
});
|
|
1283
1418
|
reasoningIndex++;
|
|
1284
1419
|
}
|
|
1285
1420
|
|
|
1286
|
-
//
|
|
1287
|
-
// reasoning-only entry) so they are not re-sent as a blank assistant turn.
|
|
1421
|
+
// Some providers reject a replayed blank assistant turn, even when it carries metadata.
|
|
1288
1422
|
if (shellIsDropped) {
|
|
1289
1423
|
return;
|
|
1290
1424
|
}
|
|
@@ -1319,6 +1453,7 @@ function convertAssistantMessage(
|
|
|
1319
1453
|
role: "assistant",
|
|
1320
1454
|
content,
|
|
1321
1455
|
...(message.name ? { name: message.name } : {}),
|
|
1456
|
+
...withAgUiMessageMetadata(message.metadata),
|
|
1322
1457
|
...(toolCalls.length + nestedToolCalls.length > 0
|
|
1323
1458
|
? {
|
|
1324
1459
|
toolCalls: [...toolCalls, ...nestedToolCalls].map(
|
|
@@ -1356,6 +1491,7 @@ function emitToolResult(
|
|
|
1356
1491
|
: typeof part.result === "string"
|
|
1357
1492
|
? part.result
|
|
1358
1493
|
: JSON.stringify(part.result);
|
|
1494
|
+
const messageMetadata = readAgUiPartMessageMetadata(part.providerMetadata);
|
|
1359
1495
|
|
|
1360
1496
|
converted.push({
|
|
1361
1497
|
id: part.unstable_toolMessageId ?? `${toolCallId}:tool`,
|
|
@@ -1363,6 +1499,7 @@ function emitToolResult(
|
|
|
1363
1499
|
content: resultContent,
|
|
1364
1500
|
toolCallId,
|
|
1365
1501
|
...(part.isError ? { error: resultContent } : {}),
|
|
1502
|
+
...(messageMetadata ? { metadata: messageMetadata } : {}),
|
|
1366
1503
|
});
|
|
1367
1504
|
}
|
|
1368
1505
|
|
|
@@ -1379,6 +1516,7 @@ function convertToolMessage(
|
|
|
1379
1516
|
content,
|
|
1380
1517
|
toolCallId,
|
|
1381
1518
|
...(typeof message.error === "string" ? { error: message.error } : {}),
|
|
1519
|
+
...withAgUiMessageMetadata(message.metadata),
|
|
1382
1520
|
});
|
|
1383
1521
|
}
|
|
1384
1522
|
|
|
@@ -1398,6 +1536,7 @@ export function toAgUiMessages(
|
|
|
1398
1536
|
role: "reasoning",
|
|
1399
1537
|
content: "",
|
|
1400
1538
|
encryptedValue: entry.encryptedValue,
|
|
1539
|
+
...(entry.metadata ? { metadata: entry.metadata } : {}),
|
|
1401
1540
|
});
|
|
1402
1541
|
for (const entry of opaqueReasoning) {
|
|
1403
1542
|
if (entry.after !== true) converted.push(toOpaqueRecord(entry));
|
|
@@ -1426,6 +1565,7 @@ export function toAgUiMessages(
|
|
|
1426
1565
|
role: "user",
|
|
1427
1566
|
content: buildUserContent(message),
|
|
1428
1567
|
...(message.name ? { name: message.name } : {}),
|
|
1568
|
+
...withAgUiMessageMetadata(message.metadata),
|
|
1429
1569
|
});
|
|
1430
1570
|
flushTrailingOpaqueReasoning();
|
|
1431
1571
|
continue;
|
|
@@ -1449,6 +1589,7 @@ export function toAgUiMessages(
|
|
|
1449
1589
|
role: wireRole,
|
|
1450
1590
|
content: extractText(message.content),
|
|
1451
1591
|
...(message.name ? { name: message.name } : {}),
|
|
1592
|
+
...withAgUiMessageMetadata(message.metadata),
|
|
1452
1593
|
});
|
|
1453
1594
|
flushTrailingOpaqueReasoning();
|
|
1454
1595
|
continue;
|
|
@@ -1459,6 +1600,7 @@ export function toAgUiMessages(
|
|
|
1459
1600
|
id: message.id,
|
|
1460
1601
|
role: "reasoning",
|
|
1461
1602
|
content: extractText(message.content),
|
|
1603
|
+
...withAgUiMessageMetadata(message.metadata),
|
|
1462
1604
|
});
|
|
1463
1605
|
flushTrailingOpaqueReasoning();
|
|
1464
1606
|
continue;
|
|
@@ -85,16 +85,16 @@ describe("RunAggregator tool approval projection", () => {
|
|
|
85
85
|
).toEqual([GATE]);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it("
|
|
88
|
+
it("keeps the gate when the finished run is then cancelled", () => {
|
|
89
89
|
const { aggregator, last } = gatedAggregator();
|
|
90
90
|
|
|
91
91
|
aggregator.handle({ type: "RUN_CANCELLED" } as any);
|
|
92
92
|
|
|
93
93
|
expect(last().status).toMatchObject({
|
|
94
|
-
type: "
|
|
95
|
-
reason: "
|
|
94
|
+
type: "requires-action",
|
|
95
|
+
reason: "interrupt",
|
|
96
96
|
});
|
|
97
|
-
expect(approvalOf(last())).
|
|
97
|
+
expect(approvalOf(last())).toBeDefined();
|
|
98
98
|
expect(
|
|
99
99
|
(last().metadata?.custom as Record<string, any> | undefined)?.agui
|
|
100
100
|
?.interrupts,
|
|
@@ -319,8 +319,14 @@ export class RunAggregator {
|
|
|
319
319
|
}
|
|
320
320
|
case "RUN_CANCELLED": {
|
|
321
321
|
// A cancel can land after RUN_FINISHED while the response is still
|
|
322
|
-
// closing
|
|
323
|
-
|
|
322
|
+
// closing: the finished answer stays complete, and an interrupt the run
|
|
323
|
+
// already delivered stays answerable.
|
|
324
|
+
if (
|
|
325
|
+
this.status?.type === "complete" ||
|
|
326
|
+
(this.status?.type === "requires-action" &&
|
|
327
|
+
this.status.reason === "interrupt")
|
|
328
|
+
)
|
|
329
|
+
break;
|
|
324
330
|
this.status = { type: "incomplete", reason: "cancelled" };
|
|
325
331
|
this.closeOpenSubagentRuns(this.status);
|
|
326
332
|
this.emit();
|
package/src/runtime/types.ts
CHANGED
|
@@ -6,11 +6,12 @@ import type {
|
|
|
6
6
|
ExternalStoreThreadListAdapter,
|
|
7
7
|
FeedbackAdapter,
|
|
8
8
|
RealtimeVoiceAdapter,
|
|
9
|
+
RunConfig,
|
|
9
10
|
SpeechSynthesisAdapter,
|
|
10
11
|
ThreadHistoryAdapter,
|
|
11
12
|
ThreadMessage,
|
|
12
13
|
} from "@assistant-ui/core";
|
|
13
|
-
import type { AbstractAgent } from "@ag-ui/client";
|
|
14
|
+
import type { AbstractAgent, Context, Message } from "@ag-ui/client";
|
|
14
15
|
import type { Logger } from "./logger";
|
|
15
16
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
16
17
|
import type { McpToolCallResult } from "./mcp-tool-result";
|
|
@@ -22,6 +23,7 @@ export const A2UI_SURFACE_ACTIVITY_TYPE = "a2ui-surface";
|
|
|
22
23
|
export type AgUiOpaqueReasoning = {
|
|
23
24
|
id: string;
|
|
24
25
|
encryptedValue: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
25
27
|
after?: boolean;
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -80,6 +82,21 @@ export type UseAgUiRuntimeOptions = ExternalStoreSharedOptions & {
|
|
|
80
82
|
agent: AbstractAgent;
|
|
81
83
|
logger?: Partial<Logger>;
|
|
82
84
|
showThinking?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Return context entries for each run. `messages` is the exact AG-UI
|
|
87
|
+
* transcript sent in the run input, after transcript selection and message
|
|
88
|
+
* conversion. On resume, it follows `resumeTranscript`: "full" sends the
|
|
89
|
+
* whole thread, while "appended" sends messages after the interrupted
|
|
90
|
+
* assistant message, falling back to the whole thread if that message is
|
|
91
|
+
* missing. `runConfig` is the run config passed while building that input.
|
|
92
|
+
* If the getter throws, the error is reported and the run continues without host entries.
|
|
93
|
+
*/
|
|
94
|
+
getRunContext?:
|
|
95
|
+
| ((input: {
|
|
96
|
+
messages: readonly Message[];
|
|
97
|
+
runConfig: RunConfig | undefined;
|
|
98
|
+
}) => readonly Context[])
|
|
99
|
+
| undefined;
|
|
83
100
|
/**
|
|
84
101
|
* What `messages` carries on a resume run, meaning a `RunAgentInput` that
|
|
85
102
|
* also carries `resume`. The AG-UI interrupt spec leaves this undefined: its
|