@springbrand/chat-client 0.1.3-alpha.41 → 0.1.3-alpha.43
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/chat-client",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@types/react-dom": "^19.2.5",
|
|
28
28
|
"react-dom": "^19.2.8",
|
|
29
29
|
"typescript": "^7.0.2",
|
|
30
|
-
"@springbrand/agent-runtime": "0.2.0-alpha.
|
|
30
|
+
"@springbrand/agent-runtime": "0.2.0-alpha.56"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
@@ -34,6 +34,12 @@ type ChatMessageMetadata = Record<string, unknown> & {
|
|
|
34
34
|
completedAt?: number;
|
|
35
35
|
error?: string;
|
|
36
36
|
interruptedByUser?: boolean;
|
|
37
|
+
// AI SDK keeps one assistant UIMessage for the live stream. This local-only
|
|
38
|
+
// anchor lets presentation place later steps below the steer that caused them.
|
|
39
|
+
optimisticSteer?: {
|
|
40
|
+
assistantMessageId: string;
|
|
41
|
+
afterStep: number;
|
|
42
|
+
};
|
|
37
43
|
requestedCapabilities?: readonly RequestedCapability[];
|
|
38
44
|
turnId?: string;
|
|
39
45
|
turnStatus?: string;
|
|
@@ -369,7 +375,35 @@ export function mergeOptimisticMessages<T extends UIMessage>(
|
|
|
369
375
|
if (optimistic.length === 0) return messages;
|
|
370
376
|
const ids = new Set(messages.map(({ id }) => id));
|
|
371
377
|
const pending = optimistic.filter(({ id }) => !ids.has(id));
|
|
372
|
-
|
|
378
|
+
if (pending.length === 0) return messages;
|
|
379
|
+
const steer = pending.find((message) =>
|
|
380
|
+
(message.metadata as ChatMessageMetadata | undefined)?.optimisticSteer
|
|
381
|
+
);
|
|
382
|
+
const anchor = (steer?.metadata as ChatMessageMetadata | undefined)
|
|
383
|
+
?.optimisticSteer;
|
|
384
|
+
const assistantIndex = anchor
|
|
385
|
+
? messages.findIndex(({ id, role }) =>
|
|
386
|
+
id === anchor.assistantMessageId && role === "assistant"
|
|
387
|
+
)
|
|
388
|
+
: -1;
|
|
389
|
+
if (!steer || !anchor || assistantIndex < 0) return [...messages, ...pending];
|
|
390
|
+
|
|
391
|
+
const assistant = messages[assistantIndex]!;
|
|
392
|
+
let step = 0;
|
|
393
|
+
const splitAt = assistant.parts.findIndex((part) => {
|
|
394
|
+
if (part.type !== "step-start") return false;
|
|
395
|
+
step += 1;
|
|
396
|
+
return step > anchor.afterStep;
|
|
397
|
+
});
|
|
398
|
+
if (splitAt < 0) return [...messages, ...pending];
|
|
399
|
+
|
|
400
|
+
return [
|
|
401
|
+
...messages.slice(0, assistantIndex),
|
|
402
|
+
{ ...assistant, id: `${assistant.id}:before:${steer.id}`, parts: assistant.parts.slice(0, splitAt) },
|
|
403
|
+
...pending,
|
|
404
|
+
{ ...assistant, id: `${assistant.id}:after:${steer.id}`, parts: assistant.parts.slice(splitAt) },
|
|
405
|
+
...messages.slice(assistantIndex + 1),
|
|
406
|
+
];
|
|
373
407
|
}
|
|
374
408
|
|
|
375
409
|
function queuedPreview(message: ChatMessage): string {
|
|
@@ -460,7 +494,7 @@ export function useUniversalAgentChat(): ChatRuntime {
|
|
|
460
494
|
() => mergeOptimisticMessages(messages, optimisticMessages),
|
|
461
495
|
[messages, optimisticMessages],
|
|
462
496
|
);
|
|
463
|
-
const
|
|
497
|
+
const reportedTurn = facetState?.turn;
|
|
464
498
|
const stoppedByUser = error?.message === USER_STOP_REASON;
|
|
465
499
|
const latestMessage = visibleMessages.at(-1);
|
|
466
500
|
const latestTurnStatus = latestMessage?.role === "assistant"
|
|
@@ -470,6 +504,18 @@ export function useUniversalAgentChat(): ChatRuntime {
|
|
|
470
504
|
latestTurnStatus === "error" ||
|
|
471
505
|
latestTurnStatus === "aborted" ||
|
|
472
506
|
latestTurnStatus === "skipped";
|
|
507
|
+
// Messages and Agent state arrive in separate frames. A terminal message for
|
|
508
|
+
// the same Submission is newer than a still-active state projection; a
|
|
509
|
+
// different active ID belongs to the next Turn and must remain live.
|
|
510
|
+
const authoritativeTurn = reportedTurn &&
|
|
511
|
+
hasTerminalMessage &&
|
|
512
|
+
latestMessage?.metadata?.turnId === reportedTurn.activeSubmissionId
|
|
513
|
+
? {
|
|
514
|
+
steerable: false,
|
|
515
|
+
hasPendingSteer: false,
|
|
516
|
+
queued: reportedTurn.queued,
|
|
517
|
+
}
|
|
518
|
+
: reportedTurn;
|
|
473
519
|
const normalizedSdkStatus = sdkStatus === "error" &&
|
|
474
520
|
(stoppedByUser || hasTerminalMessage)
|
|
475
521
|
? "ready"
|
|
@@ -753,6 +799,32 @@ export function useUniversalAgentChat(): ChatRuntime {
|
|
|
753
799
|
rollback();
|
|
754
800
|
return true;
|
|
755
801
|
}
|
|
802
|
+
if (delivery === "steer" && receipt.kind === "accepted") {
|
|
803
|
+
const activeAssistant = messagesRef.current.findLast((candidate) =>
|
|
804
|
+
candidate.role === "assistant" &&
|
|
805
|
+
candidate.metadata?.turnId === submissionId
|
|
806
|
+
);
|
|
807
|
+
const assistantMessageId = activeAssistant?.id ??
|
|
808
|
+
(authoritativeTurn?.activeSubmissionId === submissionId
|
|
809
|
+
? authoritativeTurn.activeMessageId
|
|
810
|
+
: undefined);
|
|
811
|
+
if (assistantMessageId) {
|
|
812
|
+
const afterStep = activeAssistant?.parts.filter(
|
|
813
|
+
({ type }) => type === "step-start",
|
|
814
|
+
).length ?? 0;
|
|
815
|
+
setOptimisticMessages((current) => current.map((optimistic) =>
|
|
816
|
+
optimistic.id === message.id
|
|
817
|
+
? {
|
|
818
|
+
...optimistic,
|
|
819
|
+
metadata: {
|
|
820
|
+
...optimistic.metadata,
|
|
821
|
+
optimisticSteer: { assistantMessageId, afterStep },
|
|
822
|
+
},
|
|
823
|
+
}
|
|
824
|
+
: optimistic
|
|
825
|
+
));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
756
828
|
if (turnTimingRef.current?.messageId === message.id) {
|
|
757
829
|
turnTimingRef.current.submissionId = submissionId;
|
|
758
830
|
}
|
|
@@ -785,7 +857,13 @@ export function useUniversalAgentChat(): ChatRuntime {
|
|
|
785
857
|
);
|
|
786
858
|
return false;
|
|
787
859
|
}
|
|
788
|
-
}, [
|
|
860
|
+
}, [
|
|
861
|
+
authoritativeTurn?.activeMessageId,
|
|
862
|
+
authoritativeTurn?.activeSubmissionId,
|
|
863
|
+
chatId,
|
|
864
|
+
clearError,
|
|
865
|
+
dispatchMessage,
|
|
866
|
+
]);
|
|
789
867
|
|
|
790
868
|
const sendText = useCallback(async (
|
|
791
869
|
text: string,
|