@springbrand/chat-client 0.1.3-alpha.41 → 0.1.3-alpha.42

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.41",
3
+ "version": "0.1.3-alpha.42",
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.53"
30
+ "@springbrand/agent-runtime": "0.2.0-alpha.54"
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
- return pending.length === 0 ? messages : [...messages, ...pending];
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 {
@@ -753,6 +787,32 @@ export function useUniversalAgentChat(): ChatRuntime {
753
787
  rollback();
754
788
  return true;
755
789
  }
790
+ if (delivery === "steer" && receipt.kind === "accepted") {
791
+ const activeAssistant = messagesRef.current.findLast((candidate) =>
792
+ candidate.role === "assistant" &&
793
+ candidate.metadata?.turnId === submissionId
794
+ );
795
+ const assistantMessageId = activeAssistant?.id ??
796
+ (authoritativeTurn?.activeSubmissionId === submissionId
797
+ ? authoritativeTurn.activeMessageId
798
+ : undefined);
799
+ if (assistantMessageId) {
800
+ const afterStep = activeAssistant?.parts.filter(
801
+ ({ type }) => type === "step-start",
802
+ ).length ?? 0;
803
+ setOptimisticMessages((current) => current.map((optimistic) =>
804
+ optimistic.id === message.id
805
+ ? {
806
+ ...optimistic,
807
+ metadata: {
808
+ ...optimistic.metadata,
809
+ optimisticSteer: { assistantMessageId, afterStep },
810
+ },
811
+ }
812
+ : optimistic
813
+ ));
814
+ }
815
+ }
756
816
  if (turnTimingRef.current?.messageId === message.id) {
757
817
  turnTimingRef.current.submissionId = submissionId;
758
818
  }
@@ -785,7 +845,13 @@ export function useUniversalAgentChat(): ChatRuntime {
785
845
  );
786
846
  return false;
787
847
  }
788
- }, [chatId, clearError, dispatchMessage]);
848
+ }, [
849
+ authoritativeTurn?.activeMessageId,
850
+ authoritativeTurn?.activeSubmissionId,
851
+ chatId,
852
+ clearError,
853
+ dispatchMessage,
854
+ ]);
789
855
 
790
856
  const sendText = useCallback(async (
791
857
  text: string,