@assistant-ui/react-langchain 0.0.32 → 0.0.34
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/LICENSE +1 -1
- package/dist/attachSubagentTranscripts.d.ts +13 -14
- package/dist/attachSubagentTranscripts.d.ts.map +1 -1
- package/dist/convertMessages.d.ts +6 -9
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +57 -15
- package/dist/convertMessages.js.map +1 -1
- package/dist/converter.d.ts +128 -128
- package/dist/converter.d.ts.map +1 -1
- package/dist/converter.js +2 -1
- package/dist/converter.js.map +1 -1
- package/dist/findForkCheckpointInHistory.d.ts +15 -16
- package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
- package/dist/hooks.d.ts +5 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/resolveForkCheckpoint.d.ts +4 -5
- package/dist/resolveForkCheckpoint.d.ts.map +1 -1
- package/dist/runtimeExtras.d.ts +1 -3
- package/dist/runtimeExtras.d.ts.map +1 -1
- package/dist/sdkIdentity.d.ts +1 -3
- package/dist/sdkIdentity.d.ts.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/streamingTiming.d.ts +2 -4
- package/dist/streamingTiming.d.ts.map +1 -1
- package/dist/subagentMessagesProjection.d.ts +17 -0
- package/dist/subagentMessagesProjection.d.ts.map +1 -0
- package/dist/subagentMessagesProjection.js +40 -0
- package/dist/subagentMessagesProjection.js.map +1 -0
- package/dist/types.d.ts +100 -103
- package/dist/types.d.ts.map +1 -1
- package/dist/uiMessages.d.ts +18 -6
- package/dist/uiMessages.d.ts.map +1 -1
- package/dist/uiMessages.js +33 -1
- package/dist/uiMessages.js.map +1 -1
- package/dist/useStreamRuntime.d.ts +7 -10
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +100 -26
- package/dist/useStreamRuntime.js.map +1 -1
- package/dist/useSubagentTranscripts.d.ts +3 -5
- package/dist/useSubagentTranscripts.d.ts.map +1 -1
- package/dist/useSubagentTranscripts.js +5 -3
- package/dist/useSubagentTranscripts.js.map +1 -1
- package/package.json +16 -9
- package/src/convertMessages.test.ts +153 -0
- package/src/convertMessages.ts +54 -8
- package/src/converter.ts +6 -0
- package/src/subagentMessagesProjection.test.ts +175 -0
- package/src/subagentMessagesProjection.ts +50 -0
- package/src/tests/settleOutsideAct.ts +15 -0
- package/src/uiMessages.test.ts +106 -0
- package/src/uiMessages.ts +43 -0
- package/src/useStreamRuntime.test.tsx +147 -7
- package/src/useStreamRuntime.ts +197 -45
- package/src/useStreamRuntime.voice.test.tsx +713 -0
- package/src/useSubagentTranscripts.fast-refresh.test.tsx +152 -0
- package/src/useSubagentTranscripts.test.tsx +2 -0
- package/src/useSubagentTranscripts.ts +5 -7
package/src/useStreamRuntime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/// <reference types="@assistant-ui/core/store" />
|
|
1
|
+
/// <reference types="@assistant-ui/core/store" preserve="true" />
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -19,6 +19,7 @@ import type { ThreadMessage } from "@assistant-ui/core";
|
|
|
19
19
|
import {
|
|
20
20
|
createCloudThreadListAdapterCreateFallback,
|
|
21
21
|
createToolCallCancellationStub,
|
|
22
|
+
getThreadMessageText,
|
|
22
23
|
scanPendingToolCalls,
|
|
23
24
|
} from "@assistant-ui/core/internal";
|
|
24
25
|
import {
|
|
@@ -35,7 +36,7 @@ import type {
|
|
|
35
36
|
UIMessage,
|
|
36
37
|
UseStreamRuntimeOptions,
|
|
37
38
|
} from "./types";
|
|
38
|
-
import { groupUIMessagesByParent } from "./converter";
|
|
39
|
+
import { getMessageModality, groupUIMessagesByParent } from "./converter";
|
|
39
40
|
export { groupUIMessagesByParent } from "./converter";
|
|
40
41
|
import {
|
|
41
42
|
convertLangChainBaseMessage,
|
|
@@ -49,8 +50,10 @@ import {
|
|
|
49
50
|
import { useSubagentTranscripts } from "./useSubagentTranscripts";
|
|
50
51
|
import {
|
|
51
52
|
createUIFoldMemo,
|
|
53
|
+
createUISnapshotMemo,
|
|
52
54
|
foldUIUpdates,
|
|
53
55
|
mergeUIMessages,
|
|
56
|
+
reconcileUISnapshot,
|
|
54
57
|
UI_CUSTOM_CHANNELS,
|
|
55
58
|
} from "./uiMessages";
|
|
56
59
|
import { langChainExtras } from "./runtimeExtras";
|
|
@@ -94,6 +97,15 @@ const toStagedHumanMessage = (
|
|
|
94
97
|
content: getMessageContent(msg),
|
|
95
98
|
});
|
|
96
99
|
|
|
100
|
+
const toStagedMessageInput = (message: LangChainBaseMessage) => ({
|
|
101
|
+
id: message.id,
|
|
102
|
+
type: getMessageType(message) === "ai" ? ("ai" as const) : ("human" as const),
|
|
103
|
+
content: message.content,
|
|
104
|
+
...(message.additional_kwargs && {
|
|
105
|
+
additional_kwargs: message.additional_kwargs,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
97
109
|
const humanContentText = (content: LangChainBaseMessage["content"]) => {
|
|
98
110
|
if (typeof content === "string") return content;
|
|
99
111
|
if (!Array.isArray(content)) return "";
|
|
@@ -166,7 +178,11 @@ const useStreamThreadRuntime = (
|
|
|
166
178
|
);
|
|
167
179
|
const effectiveIsRunning = stream.isLoading || hasExecutingTools;
|
|
168
180
|
|
|
169
|
-
const
|
|
181
|
+
const [uiSnapshotMemo] = useState(createUISnapshotMemo);
|
|
182
|
+
const uiStateValue = reconcileUISnapshot(
|
|
183
|
+
stream.values[uiStateKey],
|
|
184
|
+
uiSnapshotMemo,
|
|
185
|
+
);
|
|
170
186
|
|
|
171
187
|
const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);
|
|
172
188
|
const [uiFoldMemo] = useState(createUIFoldMemo);
|
|
@@ -305,6 +321,7 @@ const useStreamThreadRuntime = (
|
|
|
305
321
|
runConfig: AppendMessage["runConfig"];
|
|
306
322
|
reconcileOnEcho: boolean;
|
|
307
323
|
baseMessageCount: number;
|
|
324
|
+
transcriptStatus?: "unsent" | "sent";
|
|
308
325
|
}
|
|
309
326
|
>(),
|
|
310
327
|
);
|
|
@@ -360,19 +377,121 @@ const useStreamThreadRuntime = (
|
|
|
360
377
|
}, [stream.messages]);
|
|
361
378
|
|
|
362
379
|
const getStagedRun = (parentId: string | null) => {
|
|
363
|
-
|
|
380
|
+
const parent = parentId
|
|
381
|
+
? stagedMessagesRef.current.get(parentId)
|
|
382
|
+
: undefined;
|
|
383
|
+
if (!parent || parent.transcriptStatus === "sent") return null;
|
|
364
384
|
|
|
365
385
|
const staged: LangChainBaseMessage[] = [];
|
|
366
386
|
for (const message of visibleMessagesRef.current) {
|
|
367
|
-
|
|
368
|
-
|
|
387
|
+
const entry = message.id
|
|
388
|
+
? stagedMessagesRef.current.get(message.id)
|
|
389
|
+
: undefined;
|
|
390
|
+
if (entry && entry.transcriptStatus !== "sent") {
|
|
391
|
+
staged.push(entry.message);
|
|
369
392
|
}
|
|
370
393
|
if (message.id === parentId) break;
|
|
371
394
|
}
|
|
372
395
|
|
|
373
396
|
return {
|
|
374
397
|
messages: staged,
|
|
375
|
-
runConfig:
|
|
398
|
+
runConfig: parent.runConfig,
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const appendVoiceTranscript = (message: ThreadMessage) => {
|
|
403
|
+
const transcript = {
|
|
404
|
+
id: message.id,
|
|
405
|
+
_getType: () => (message.role === "assistant" ? "ai" : "human"),
|
|
406
|
+
content: getThreadMessageText(message),
|
|
407
|
+
...(message.metadata.modality && {
|
|
408
|
+
additional_kwargs: { modality: message.metadata.modality },
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
stagedMessagesRef.current.set(transcript.id, {
|
|
412
|
+
message: transcript,
|
|
413
|
+
runConfig: undefined,
|
|
414
|
+
reconcileOnEcho: false,
|
|
415
|
+
baseMessageCount: streamRef.current.messages.length,
|
|
416
|
+
transcriptStatus: "unsent",
|
|
417
|
+
});
|
|
418
|
+
const nextMessages = [...visibleMessagesRef.current, transcript];
|
|
419
|
+
visibleMessagesRef.current = nextMessages;
|
|
420
|
+
setStagedMessages(nextMessages);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const getUnsentTranscripts = () =>
|
|
424
|
+
visibleMessagesRef.current.filter(
|
|
425
|
+
(message) =>
|
|
426
|
+
message.id !== undefined &&
|
|
427
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus ===
|
|
428
|
+
"unsent",
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const setTranscriptStatus = (
|
|
432
|
+
messages: readonly LangChainBaseMessage[],
|
|
433
|
+
status: "unsent" | "sent",
|
|
434
|
+
) => {
|
|
435
|
+
for (const message of messages) {
|
|
436
|
+
const staged = message.id
|
|
437
|
+
? stagedMessagesRef.current.get(message.id)
|
|
438
|
+
: undefined;
|
|
439
|
+
if (staged?.transcriptStatus) staged.transcriptStatus = status;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Reserving before the submit keeps an overlapping submit from carrying the
|
|
444
|
+
// same transcript; a failed submit hands it back to the next run.
|
|
445
|
+
const submitCarryingTranscripts = async (
|
|
446
|
+
transcripts: readonly LangChainBaseMessage[],
|
|
447
|
+
submit: () => Promise<void>,
|
|
448
|
+
) => {
|
|
449
|
+
setTranscriptStatus(transcripts, "sent");
|
|
450
|
+
try {
|
|
451
|
+
await submit();
|
|
452
|
+
} catch (error) {
|
|
453
|
+
setTranscriptStatus(transcripts, "unsent");
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {
|
|
459
|
+
for (const message of messages) {
|
|
460
|
+
if (
|
|
461
|
+
message.id &&
|
|
462
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus
|
|
463
|
+
)
|
|
464
|
+
removeStagedMessage(message.id);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const isTranscriptMessage = (message: LangChainBaseMessage) => {
|
|
469
|
+
const staged = message.id
|
|
470
|
+
? stagedMessagesRef.current.get(message.id)
|
|
471
|
+
: undefined;
|
|
472
|
+
if (staged?.transcriptStatus === "unsent") return true;
|
|
473
|
+
const type = getMessageType(message);
|
|
474
|
+
return (
|
|
475
|
+
(type === "human" || type === "ai") &&
|
|
476
|
+
getMessageModality(message.additional_kwargs) !== undefined
|
|
477
|
+
);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// A transcript reaches the graph in the same input as the message after it, so
|
|
481
|
+
// no checkpoint ends at one; a fork starts before the trailing transcripts.
|
|
482
|
+
const planForkTranscripts = (parentId: string | null) => {
|
|
483
|
+
const visible = visibleMessagesRef.current;
|
|
484
|
+
const parentIndex =
|
|
485
|
+
parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);
|
|
486
|
+
if (parentId != null && parentIndex === -1)
|
|
487
|
+
return { forkParentId: parentId, transcripts: [], truncated: [] };
|
|
488
|
+
|
|
489
|
+
let start = parentIndex + 1;
|
|
490
|
+
while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;
|
|
491
|
+
return {
|
|
492
|
+
forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,
|
|
493
|
+
transcripts: visible.slice(start, parentIndex + 1),
|
|
494
|
+
truncated: visible.slice(parentIndex + 1),
|
|
376
495
|
};
|
|
377
496
|
};
|
|
378
497
|
|
|
@@ -454,7 +573,10 @@ const useStreamThreadRuntime = (
|
|
|
454
573
|
autoCancelPendingToolCalls !== false
|
|
455
574
|
? getPendingToolCalls(
|
|
456
575
|
streamRef.current.messages as readonly LangChainBaseMessage[],
|
|
457
|
-
)
|
|
576
|
+
)
|
|
577
|
+
// LangChain rejects a tool message without a tool_call_id.
|
|
578
|
+
.filter((toolCall) => toolCall.id)
|
|
579
|
+
.map(createToolCallCancellationStub)
|
|
458
580
|
: [];
|
|
459
581
|
// A null threadId is not a no-op for the SDK: it rebinds the controller
|
|
460
582
|
// away from its self-created thread and forces a fresh one, so the
|
|
@@ -462,27 +584,32 @@ const useStreamThreadRuntime = (
|
|
|
462
584
|
// longer holds appends on that barrier.
|
|
463
585
|
try {
|
|
464
586
|
const { externalId } = await aui.threadListItem.initialize();
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
587
|
+
const transcripts = getUnsentTranscripts();
|
|
588
|
+
await submitCarryingTranscripts(transcripts, () =>
|
|
589
|
+
streamRef.current.submit(
|
|
590
|
+
{
|
|
591
|
+
[messagesKey]: [
|
|
592
|
+
...cancellations,
|
|
593
|
+
...transcripts.map(toStagedMessageInput),
|
|
594
|
+
{
|
|
595
|
+
id: stagedMessageId,
|
|
596
|
+
type: "human",
|
|
597
|
+
content,
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
...runConfigToSubmitOptions(msg.runConfig),
|
|
603
|
+
...(externalId != null ? { threadId: externalId } : {}),
|
|
604
|
+
},
|
|
605
|
+
),
|
|
480
606
|
);
|
|
481
607
|
} catch (error) {
|
|
482
608
|
removeStagedMessage(stagedMessageId);
|
|
483
609
|
throw error;
|
|
484
610
|
}
|
|
485
611
|
},
|
|
612
|
+
onVoiceTranscript: appendVoiceTranscript,
|
|
486
613
|
onAddToolResult: async ({
|
|
487
614
|
messageId,
|
|
488
615
|
toolCallId,
|
|
@@ -513,9 +640,18 @@ const useStreamThreadRuntime = (
|
|
|
513
640
|
onReload: async (parentId, config) => {
|
|
514
641
|
const stagedRun = getStagedRun(parentId);
|
|
515
642
|
if (stagedRun) {
|
|
643
|
+
if (
|
|
644
|
+
config.sourceId &&
|
|
645
|
+
stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus
|
|
646
|
+
)
|
|
647
|
+
removeStagedMessage(config.sourceId);
|
|
516
648
|
const promotedIds = new Set<string>();
|
|
517
649
|
for (const message of stagedRun.messages) {
|
|
518
|
-
if (
|
|
650
|
+
if (
|
|
651
|
+
!message.id ||
|
|
652
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus
|
|
653
|
+
)
|
|
654
|
+
continue;
|
|
519
655
|
promotedIds.add(message.id);
|
|
520
656
|
stagedMessagesRef.current.delete(message.id);
|
|
521
657
|
}
|
|
@@ -531,15 +667,13 @@ const useStreamThreadRuntime = (
|
|
|
531
667
|
}
|
|
532
668
|
const runConfig = config.runConfig ?? stagedRun.runConfig;
|
|
533
669
|
setActiveRunConfig(runConfig);
|
|
534
|
-
await
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
},
|
|
542
|
-
runConfigToSubmitOptions(runConfig),
|
|
670
|
+
await submitCarryingTranscripts(stagedRun.messages, () =>
|
|
671
|
+
stream.submit(
|
|
672
|
+
{
|
|
673
|
+
[messagesKey]: stagedRun.messages.map(toStagedMessageInput),
|
|
674
|
+
},
|
|
675
|
+
runConfigToSubmitOptions(runConfig),
|
|
676
|
+
),
|
|
543
677
|
);
|
|
544
678
|
return;
|
|
545
679
|
}
|
|
@@ -547,21 +681,30 @@ const useStreamThreadRuntime = (
|
|
|
547
681
|
const threadId = externalId;
|
|
548
682
|
if (!threadId || parentId == null) return;
|
|
549
683
|
const s = streamRef.current;
|
|
684
|
+
const fork = planForkTranscripts(parentId);
|
|
550
685
|
const checkpointId = await resolveForkCheckpoint(
|
|
551
686
|
s.client,
|
|
552
687
|
threadId,
|
|
553
688
|
s.messages as readonly LangChainBaseMessage[],
|
|
554
|
-
|
|
689
|
+
fork.forkParentId,
|
|
555
690
|
config.sourceId,
|
|
556
691
|
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
557
692
|
messagesKey,
|
|
558
693
|
);
|
|
559
694
|
if (!checkpointId) return;
|
|
695
|
+
dropTranscripts(fork.truncated);
|
|
560
696
|
setActiveRunConfig(config.runConfig);
|
|
561
|
-
await
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
697
|
+
await submitCarryingTranscripts(fork.transcripts, () =>
|
|
698
|
+
s.submit(
|
|
699
|
+
fork.transcripts.length > 0
|
|
700
|
+
? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }
|
|
701
|
+
: null,
|
|
702
|
+
{
|
|
703
|
+
forkFrom: checkpointId,
|
|
704
|
+
...runConfigToSubmitOptions(config.runConfig),
|
|
705
|
+
},
|
|
706
|
+
),
|
|
707
|
+
);
|
|
565
708
|
},
|
|
566
709
|
onEdit: async (message) => {
|
|
567
710
|
if (!(message.startRun ?? message.role === "user")) {
|
|
@@ -586,24 +729,33 @@ const useStreamThreadRuntime = (
|
|
|
586
729
|
const threadId = externalId;
|
|
587
730
|
if (!threadId) return;
|
|
588
731
|
const s = streamRef.current;
|
|
732
|
+
const fork = planForkTranscripts(message.parentId);
|
|
589
733
|
const checkpointId = await resolveForkCheckpoint(
|
|
590
734
|
s.client,
|
|
591
735
|
threadId,
|
|
592
736
|
s.messages as readonly LangChainBaseMessage[],
|
|
593
|
-
|
|
737
|
+
fork.forkParentId,
|
|
594
738
|
message.sourceId,
|
|
595
739
|
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
596
740
|
messagesKey,
|
|
597
741
|
);
|
|
598
742
|
if (!checkpointId) return;
|
|
743
|
+
dropTranscripts(fork.truncated);
|
|
599
744
|
const content = getMessageContent(message);
|
|
600
745
|
setActiveRunConfig(message.runConfig);
|
|
601
|
-
await
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
746
|
+
await submitCarryingTranscripts(fork.transcripts, () =>
|
|
747
|
+
s.submit(
|
|
748
|
+
{
|
|
749
|
+
[messagesKey]: [
|
|
750
|
+
...fork.transcripts.map(toStagedMessageInput),
|
|
751
|
+
{ type: "human", content },
|
|
752
|
+
],
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
forkFrom: checkpointId,
|
|
756
|
+
...runConfigToSubmitOptions(message.runConfig),
|
|
757
|
+
},
|
|
758
|
+
),
|
|
607
759
|
);
|
|
608
760
|
},
|
|
609
761
|
onCancel:
|