@agents24/react 0.2.25 → 0.2.27

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.
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "schemaVersion": "agents24.compatibility.v1",
49
49
  "targetOperationCounts": {
50
- "client": 31,
51
- "node": 139,
52
- "python": 132
50
+ "client": 36,
51
+ "node": 196,
52
+ "python": 190
53
53
  }
54
54
  }
@@ -0,0 +1,18 @@
1
+ /** Ephemeral display only: these fragments never enter stored messages or replay cursors. */
2
+ import type { AssistantPreview } from "@agents24/client/protocol";
3
+ import type { RuntimeEvent } from "@agents24/client";
4
+ import type { ChatMessage } from "./model";
5
+ export type AssistantPreviewState = {
6
+ runId: string;
7
+ key: string;
8
+ segmentId: string;
9
+ committed: number;
10
+ dispatchId: string;
11
+ generation: number;
12
+ chunks: Record<number, string>;
13
+ retired: string[];
14
+ };
15
+ export declare function acceptAssistantPreview(current: AssistantPreviewState | null | undefined, frame: AssistantPreview | null): AssistantPreviewState | null;
16
+ export declare function commitAssistantPreview(current: AssistantPreviewState | null | undefined, event: RuntimeEvent): AssistantPreviewState | null;
17
+ export declare function previewText(state: AssistantPreviewState | null | undefined): string;
18
+ export declare function messagesWithAssistantPreview(messages: ChatMessage[], state: AssistantPreviewState | null | undefined): ChatMessage[];
package/dist/index.js CHANGED
@@ -637,6 +637,79 @@ function createRuntimeEventFrameBuffer(apply, scheduleFrame) {
637
637
  };
638
638
  }
639
639
 
640
+ // src/assistant-preview.ts
641
+ var keyOf = (frame) => `${frame.dispatch_id}:${frame.generation}:${frame.segment_id}`;
642
+ function acceptAssistantPreview(current, frame) {
643
+ if (!frame) return current ? { ...current, chunks: {} } : null;
644
+ const key = keyOf(frame);
645
+ if (current?.runId === frame.run_id && current.dispatchId === frame.dispatch_id && current.generation > frame.generation) return current;
646
+ if (current?.runId === frame.run_id && current.retired.includes(key)) return current;
647
+ const state = current?.runId === frame.run_id && current.key === key ? current : {
648
+ runId: frame.run_id,
649
+ dispatchId: frame.dispatch_id,
650
+ generation: frame.generation,
651
+ key,
652
+ segmentId: frame.segment_id,
653
+ committed: -1,
654
+ chunks: {},
655
+ retired: current?.runId === frame.run_id ? [...current.retired, current.key].slice(-8) : []
656
+ };
657
+ if (frame.chunk_index <= state.committed || Object.keys(state.chunks).length >= 32 || Object.values(state.chunks).reduce((size, text2) => size + text2.length, 0) + frame.text.length > 65536) return state;
658
+ return { ...state, chunks: { ...state.chunks, [frame.chunk_index]: frame.text } };
659
+ }
660
+ function commitAssistantPreview(current, event) {
661
+ if (event.event === "run.accepted" && current && current.runId !== event.run_id) return null;
662
+ if (current && current.runId !== event.run_id) return current;
663
+ if (["assistant.completed", "run.completed", "run.failed", "run.cancelled", "run.waiting", "run.interrupted", "run.paused", "run.snapshot"].includes(event.event)) return null;
664
+ const ack = event.payload.preview_ack;
665
+ if (event.event !== "assistant.delta" || !ack || typeof ack.dispatch_id !== "string" || typeof ack.segment_id !== "string" || !Number.isSafeInteger(ack.generation) || !Number.isSafeInteger(ack.chunk_index)) return current || null;
666
+ const key = keyOf(ack);
667
+ if (current && current.key !== key) return current;
668
+ const committed = Math.max(current?.committed ?? -1, ack.chunk_index);
669
+ return {
670
+ runId: event.run_id,
671
+ dispatchId: ack.dispatch_id,
672
+ generation: ack.generation,
673
+ key,
674
+ segmentId: ack.segment_id,
675
+ committed,
676
+ retired: current?.retired || [],
677
+ chunks: Object.fromEntries(Object.entries(current?.chunks || {}).filter(([index]) => Number(index) > committed))
678
+ };
679
+ }
680
+ function previewText(state) {
681
+ if (!state) return "";
682
+ let text2 = "";
683
+ for (let index = state.committed + 1; Object.hasOwn(state.chunks, index); index++) text2 += state.chunks[index];
684
+ return text2;
685
+ }
686
+ function messagesWithAssistantPreview(messages, state) {
687
+ const tail = previewText(state);
688
+ if (!state || !tail) return messages;
689
+ let index = -1;
690
+ for (let i = messages.length - 1; i >= 0; i--) {
691
+ if (messages[i].role === "assistant" && messages[i].runId === state.runId && !messages[i].isFinal) {
692
+ index = i;
693
+ break;
694
+ }
695
+ }
696
+ const base = index < 0 ? {
697
+ id: `preview-${state.runId}`,
698
+ role: "assistant",
699
+ runId: state.runId,
700
+ content: "",
701
+ parts: [],
702
+ createdAt: /* @__PURE__ */ new Date(0),
703
+ isFinal: false
704
+ } : messages[index];
705
+ const parts = [...base.parts];
706
+ const last = parts[parts.length - 1];
707
+ if (last?.type === "text") parts[parts.length - 1] = { ...last, text: last.text + tail };
708
+ else parts.push({ id: `preview-${state.segmentId}`, type: "text", kind: "text", text: tail });
709
+ const rendered = { ...base, content: base.content + tail, parts };
710
+ return index < 0 ? [...messages, rendered] : messages.map((message, i) => i === index ? rendered : message);
711
+ }
712
+
640
713
  // src/hitl-resolution.ts
641
714
  function applyHitlResolution(messages, receipt) {
642
715
  return messages.map((message) => message.runId !== receipt.run_id ? message : {
@@ -869,6 +942,11 @@ function reconcileModelTurnParts(parts, event) {
869
942
  return active ? upsertResponseBlockParts(next, [active]) : next;
870
943
  }
871
944
  function applyRuntimeEvent(state, event) {
945
+ const next = applyCanonicalRuntimeEvent(state, event);
946
+ if (!state.assistantPreview && !event.payload.preview_ack) return next;
947
+ return { ...next, assistantPreview: commitAssistantPreview(state.assistantPreview, event) };
948
+ }
949
+ function applyCanonicalRuntimeEvent(state, event) {
872
950
  if (state.activeRunId && event.run_id && event.run_id !== state.activeRunId) {
873
951
  const previous = state.messages.find((message) => message.role === "assistant" && message.runId === event.run_id);
874
952
  if (event.event !== "run.accepted") {
@@ -2130,6 +2208,12 @@ function useAgentChat(options = {}) {
2130
2208
  }
2131
2209
  }
2132
2210
  }, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update, invalidateThreadOpen, ownOperation, captureFeedbackRead]);
2211
+ const consumePreview = React8.useCallback((frame) => {
2212
+ update((current) => {
2213
+ if (frame && current.activeRunId && current.activeRunId !== frame.run_id) return current;
2214
+ return { ...current, assistantPreview: acceptAssistantPreview(current.assistantPreview, frame) };
2215
+ });
2216
+ }, [update]);
2133
2217
  const consumeEvent = React8.useCallback((event) => {
2134
2218
  const timing = continuationTiming.current.get(event.run_id);
2135
2219
  if (timing && !timing.attached) {
@@ -2158,7 +2242,9 @@ function useAgentChat(options = {}) {
2158
2242
  clearOperationError("attach_run");
2159
2243
  update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
2160
2244
  try {
2161
- const result = await client.runs.attach({ runId, cursor, leaseSeconds: 60, signal: controller.signal }, (event) => {
2245
+ const result = await client.runs.attach({ runId, cursor, onPreview: (frame) => {
2246
+ if (isCurrent()) consumePreview(frame);
2247
+ }, leaseSeconds: 60, signal: controller.signal }, (event) => {
2162
2248
  if (isCurrent()) consumeEvent(event);
2163
2249
  });
2164
2250
  if (!isCurrent()) return;
@@ -2174,7 +2260,7 @@ function useAgentChat(options = {}) {
2174
2260
  } finally {
2175
2261
  if (streamControllerRef.current === controller) streamControllerRef.current = null;
2176
2262
  }
2177
- }, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update, ownOperation]);
2263
+ }, [clearOperationError, client, consumeEvent, consumePreview, eventBuffer, failOperation, refreshMessages, runtime, update, ownOperation]);
2178
2264
  attachRunRef.current = attachRun;
2179
2265
  const submit = React8.useCallback(async (input) => {
2180
2266
  if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
@@ -2285,6 +2371,9 @@ function useAgentChat(options = {}) {
2285
2371
  }));
2286
2372
  }
2287
2373
  const result = await client.chat.stream({
2374
+ onPreview: (frame) => {
2375
+ if (isCurrent()) consumePreview(frame);
2376
+ },
2288
2377
  leaseSeconds: 60,
2289
2378
  clientSubmissionId: ticket.client_submission_id,
2290
2379
  input: input.text.trim(),
@@ -2336,7 +2425,7 @@ function useAgentChat(options = {}) {
2336
2425
  } catch (error) {
2337
2426
  if (submissionOwner() || !isAbortLike(error)) throw error;
2338
2427
  }
2339
- }, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update, ownOperation]);
2428
+ }, [clearOperationError, client, consumeEvent, consumePreview, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update, ownOperation]);
2340
2429
  const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
2341
2430
  queue,
2342
2431
  stateRef,
@@ -2563,6 +2652,7 @@ function useAgentChat(options = {}) {
2563
2652
  const hasMoreThreads = threadTotal === void 0 ? state.threads.length >= threadPageSize : state.threads.length < threadTotal;
2564
2653
  return {
2565
2654
  ...state,
2655
+ messages: messagesWithAssistantPreview(state.messages, state.assistantPreview),
2566
2656
  activeThread,
2567
2657
  pendingMessages,
2568
2658
  messageQueue,