@invergent/agent-chat-react 2.15.9 → 2.16.0

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/dist/index.cjs CHANGED
@@ -10882,6 +10882,17 @@ function useSmoothStream(target, isStreaming) {
10882
10882
  return displayedLen >= target.length ? target : target.slice(0, displayedLen);
10883
10883
  }
10884
10884
 
10885
+ // src/runtime/reasoning-tokens.ts
10886
+ function formatReasoningTokens(tokens) {
10887
+ if (tokens < 1e3) return String(tokens);
10888
+ return `${(tokens / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
10889
+ }
10890
+ function reasoningTokenLabel(message, live) {
10891
+ const tokens = message.reasoningTokens ?? message.reasoningDeltaCount;
10892
+ if (tokens === void 0) return live ? "Thinking..." : "Thought";
10893
+ return `${live ? "Thinking..." : "Thought \xB7"} ${formatReasoningTokens(tokens)} tokens`;
10894
+ }
10895
+
10885
10896
  // src/lib/next-action.ts
10886
10897
  var _NEXT_ACTION_RE = /<next_action([^>]*)>([\s\S]*?)<\/next_action>\s*/gi;
10887
10898
  var _PARTIAL_NEXT_ACTION_FOOTER_RE = /(?:^|\n)\s*<(?:n(?:e(?:x(?:t(?:_(?:a(?:c(?:t(?:i(?:o(?:n[\s\S]*)?)?)?)?)?)?)?)?)?)?)?$/i;
@@ -12452,7 +12463,7 @@ function SimpleFinalAnswer({
12452
12463
  function deriveIterationLabel(message) {
12453
12464
  const calls = visibleToolCalls(message);
12454
12465
  if (calls.length === 0) {
12455
- return message.reasoning ? "Thought through the problem" : null;
12466
+ return message.reasoning || message.reasoningTokens ? reasoningTokenLabel(message, false) : null;
12456
12467
  }
12457
12468
  if (calls.every((tc) => tc.toolName === "skill_view")) {
12458
12469
  return skillViewLabel(calls);
@@ -12484,11 +12495,28 @@ function isIterationLive(message) {
12484
12495
  }
12485
12496
  return message.status === "streaming";
12486
12497
  }
12498
+ function hasInlineRunningIndicator(messages, viewMode) {
12499
+ for (let i = messages.length - 1; i >= 0; i--) {
12500
+ const message = messages[i];
12501
+ if (message.role === "user") return false;
12502
+ if (message.role !== "assistant") continue;
12503
+ if (viewMode === "simple") {
12504
+ if (message.toolCalls?.some((tc) => tc.toolName === "ask_user_question")) {
12505
+ return false;
12506
+ }
12507
+ return isIterationLive(message) && (!!message.toolCalls?.length || !message.content || message.content === message.reasoning);
12508
+ }
12509
+ return messageToEntries(message, i === messages.length - 1, {}).some(
12510
+ (entry) => entry.kind === "reasoning" && entry.isStreaming
12511
+ );
12512
+ }
12513
+ return false;
12514
+ }
12487
12515
  function liveIterationLabel(message) {
12488
12516
  const running = (message.toolCalls ?? []).filter(
12489
12517
  (tc) => tc.status === "running" && !isHiddenSimpleTool(tc)
12490
12518
  );
12491
- if (running.length === 0) return "Thinking\u2026";
12519
+ if (running.length === 0) return reasoningTokenLabel(message, true);
12492
12520
  if (running.every((tc) => tc.toolName === "skill_view")) {
12493
12521
  return `${skillViewLabel(running)}\u2026`;
12494
12522
  }
@@ -12545,44 +12573,16 @@ function SimpleDetailRow({ icon: Icon, children }) {
12545
12573
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
12546
12574
  ] });
12547
12575
  }
12548
- function ClampedReasoning({
12549
- text,
12550
- previewParagraphs = 2
12551
- }) {
12552
- const paragraphs = splitReasoningParagraphs(text);
12553
- const [expanded, setExpanded] = react.useState(false);
12554
- const hasMore = paragraphs.length > previewParagraphs;
12555
- const visible = expanded || !hasMore ? paragraphs : paragraphs.slice(0, previewParagraphs);
12556
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
12557
- visible.map((paragraph, i) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap", children: paragraph }, i)),
12558
- hasMore && /* @__PURE__ */ jsxRuntime.jsx(
12559
- "button",
12560
- {
12561
- type: "button",
12562
- onClick: () => setExpanded((prev) => !prev),
12563
- className: "text-xs font-medium text-foreground hover:text-foreground/70 cursor-pointer",
12564
- children: expanded ? "Show less" : "Show more..."
12565
- }
12566
- )
12567
- ] });
12568
- }
12569
- function splitReasoningParagraphs(text) {
12570
- const trimmed = text.trim();
12571
- if (!trimmed) return [];
12572
- const blankSplit = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
12573
- if (blankSplit.length > 1) return blankSplit;
12574
- return trimmed.split(/\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
12575
- }
12576
12576
  function IterationExpanded({
12577
12577
  message
12578
12578
  }) {
12579
12579
  const calls = visibleToolCalls(message);
12580
- const reasoning = (message.reasoning ?? "").trim();
12580
+ const hasReasoning = !!message.reasoning || !!message.reasoningTokens;
12581
12581
  const anyVisibleRunning = calls.some((tc) => tc.status === "running");
12582
- const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
12583
- if (!reasoning && calls.length === 0) return null;
12582
+ const showDone = !anyVisibleRunning && (calls.length > 0 || hasReasoning);
12583
+ if (!hasReasoning && calls.length === 0) return null;
12584
12584
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-2 space-y-3 border-l border-border/40 pl-4 py-1", children: [
12585
- reasoning && /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: lucideReact.ClockIcon, children: /* @__PURE__ */ jsxRuntime.jsx(ClampedReasoning, { text: reasoning }) }),
12585
+ hasReasoning && /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: lucideReact.ClockIcon, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: reasoningTokenLabel(message, false) }) }),
12586
12586
  calls.map((tc) => {
12587
12587
  if (tc.toolName === "todo") {
12588
12588
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsxRuntime.jsx(TodoToolBlock, { tc }) }, tc.id);
@@ -12597,6 +12597,7 @@ function IterationExpanded({
12597
12597
  }
12598
12598
  function IterationGroup({
12599
12599
  message,
12600
+ isActive = true,
12600
12601
  sessionId,
12601
12602
  artifactFallbacks,
12602
12603
  onFileSelect
@@ -12616,7 +12617,7 @@ function IterationGroup({
12616
12617
  }
12617
12618
  ) });
12618
12619
  }
12619
- if (isIterationLive(message)) {
12620
+ if (isActive && isIterationLive(message)) {
12620
12621
  return /* @__PURE__ */ jsxRuntime.jsx(
12621
12622
  OrbShimmerLabel,
12622
12623
  {
@@ -12630,6 +12631,9 @@ function IterationGroup({
12630
12631
  if (!label) {
12631
12632
  return null;
12632
12633
  }
12634
+ if (!summary && visibleToolCalls(message).length === 0) {
12635
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-0.5 text-sm text-muted-foreground", children: label });
12636
+ }
12633
12637
  const labelTone = summary ? "text-muted-foreground italic" : "text-muted-foreground";
12634
12638
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
12635
12639
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -12723,6 +12727,7 @@ function LoopResultAffordance({ message }) {
12723
12727
  }
12724
12728
  function SimpleAssistantGroup({
12725
12729
  messages,
12730
+ activeAssistantId,
12726
12731
  isRunning,
12727
12732
  sessionId,
12728
12733
  artifactFallbacks,
@@ -12734,7 +12739,7 @@ function SimpleAssistantGroup({
12734
12739
  const tail = assistantMessages[assistantMessages.length - 1];
12735
12740
  const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
12736
12741
  const tailIsTextOnly = !!tail && !tailHasTools;
12737
- const finalText = tailIsTextOnly && tail.content ? tail.content : "";
12742
+ const finalText = tailIsTextOnly && tail.content !== tail.reasoning ? tail.content : "";
12738
12743
  const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
12739
12744
  const turnComplete = !isRunning && tail?.status === "complete";
12740
12745
  const summaryPref = finalText !== null ? stripAndParseNextAction(finalText).action?.summary ?? "hide" : "hide";
@@ -12771,7 +12776,7 @@ function SimpleAssistantGroup({
12771
12776
  );
12772
12777
  }
12773
12778
  if (message === tail && tailIsTextOnly && finalText) return null;
12774
- const rawContent = (message.content ?? "").trim();
12779
+ const rawContent = message.content === message.reasoning ? "" : (message.content ?? "").trim();
12775
12780
  const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
12776
12781
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
12777
12782
  const askBody = hasUserFacingAskContent(message) ? cleaned : null;
@@ -12781,6 +12786,7 @@ function SimpleAssistantGroup({
12781
12786
  IterationGroup,
12782
12787
  {
12783
12788
  message,
12789
+ isActive: message.id === activeAssistantId,
12784
12790
  sessionId,
12785
12791
  artifactFallbacks,
12786
12792
  onFileSelect
@@ -12813,6 +12819,7 @@ function SimpleAssistantGroup({
12813
12819
  }
12814
12820
  function AssistantGroup({
12815
12821
  messages,
12822
+ activeAssistantId,
12816
12823
  lastGlobalIndex,
12817
12824
  totalMessages,
12818
12825
  isRunning,
@@ -12828,6 +12835,7 @@ function AssistantGroup({
12828
12835
  SimpleAssistantGroup,
12829
12836
  {
12830
12837
  messages,
12838
+ activeAssistantId,
12831
12839
  isRunning,
12832
12840
  sessionId,
12833
12841
  artifactFallbacks,
@@ -12959,6 +12967,15 @@ function ChatThread({
12959
12967
  onOpenIntegrations
12960
12968
  }) {
12961
12969
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
12970
+ const activeAssistantId = react.useMemo(() => {
12971
+ if (!isRunning) return void 0;
12972
+ for (let i = messages.length - 1; i >= 0; i--) {
12973
+ const message = messages[i];
12974
+ if (message.role === "user") return void 0;
12975
+ if (message.role === "assistant") return message.id;
12976
+ }
12977
+ return void 0;
12978
+ }, [messages, isRunning]);
12962
12979
  const pendingAsk = react.useMemo(() => pendingAskCall(messages), [messages]);
12963
12980
  const askPolicy = react.useMemo(
12964
12981
  () => askComposerPolicy(pendingAsk),
@@ -12966,13 +12983,26 @@ function ChatThread({
12966
12983
  );
12967
12984
  const awaitingInput = askPolicy !== null;
12968
12985
  const orbActivity = react.useMemo(
12969
- () => deriveOrbActivity(
12970
- messages,
12971
- viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12972
- ),
12986
+ () => {
12987
+ const activity = deriveOrbActivity(
12988
+ messages,
12989
+ viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12990
+ );
12991
+ if (viewMode === "simple" && activity.state === "solving") {
12992
+ const latest = [...messages].reverse().find((message) => message.role === "assistant");
12993
+ if (latest && !latest.toolCalls?.some((tc) => tc.status === "running")) {
12994
+ return { ...activity, label: reasoningTokenLabel(latest, true) };
12995
+ }
12996
+ }
12997
+ return activity;
12998
+ },
12973
12999
  [messages, viewMode]
12974
13000
  );
12975
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
13001
+ const inlineRunningIndicator = react.useMemo(
13002
+ () => hasInlineRunningIndicator(messages, viewMode),
13003
+ [messages, viewMode]
13004
+ );
13005
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput && !inlineRunningIndicator;
12976
13006
  const composerLocked = askPolicy?.locked ?? false;
12977
13007
  const composerDisabled = disabled || composerLocked;
12978
13008
  const composerDisabledReason = composerLocked ? askPolicy?.reason : disabledReason;
@@ -13158,6 +13188,7 @@ function ChatThread({
13158
13188
  AssistantGroup,
13159
13189
  {
13160
13190
  messages: group.messages,
13191
+ activeAssistantId,
13161
13192
  lastGlobalIndex: group.lastGlobalIndex,
13162
13193
  totalMessages: messages.length,
13163
13194
  isRunning,
@@ -13403,8 +13434,17 @@ function applyAgentChatEvent(state, event) {
13403
13434
  return collectResearchSource(withResult, toolName, event.data);
13404
13435
  }
13405
13436
  case "harness.wake":
13406
- case "llm.request":
13407
13437
  return { ...nextState, terminal: false, isRunning: true };
13438
+ case "llm.request":
13439
+ return {
13440
+ ...nextState,
13441
+ terminal: false,
13442
+ isRunning: true,
13443
+ hadDeltas: false,
13444
+ messages: nextState.messages.map(
13445
+ (message) => message.role === "assistant" && message.status === "streaming" && !message.toolCalls?.some((tc) => tc.status === "running") ? { ...message, status: "complete" } : message
13446
+ )
13447
+ };
13408
13448
  case "harness.crash":
13409
13449
  case "stream.timeout":
13410
13450
  return nextState;
@@ -13621,22 +13661,31 @@ function browserMarker(event) {
13621
13661
  function applyLlmDelta(state, event) {
13622
13662
  const deltaContent = stringValue2(event.data.content);
13623
13663
  const deltaReasoning = stringValue2(event.data.reasoning);
13664
+ const reasoningTokens = typeof event.data.reasoning_tokens === "number" && Number.isInteger(event.data.reasoning_tokens) && event.data.reasoning_tokens >= 0 ? event.data.reasoning_tokens : void 0;
13624
13665
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13625
13666
  const messages = [...state.messages];
13626
13667
  const lastIdx = findLastAssistantIndex(messages);
13627
13668
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
13628
13669
  const hasUserAfter = lastIdx >= 0 && hasUserAfterIndex(messages, lastIdx);
13670
+ const matchesIteration = (turnId === void 0 || lastMsg?.turnId === turnId) && (iterationIndex === void 0 || lastMsg?.iterationIndex === iterationIndex);
13671
+ const sameIteration = turnId !== void 0 && iterationIndex !== void 0 && matchesIteration;
13672
+ if (reasoningTokens !== void 0 && !deltaContent && !deltaReasoning && lastMsg && !hasUserAfter && matchesIteration) {
13673
+ messages[lastIdx] = { ...lastMsg, reasoningTokens };
13674
+ return { ...state, messages };
13675
+ }
13629
13676
  const allToolsDone = Boolean(
13630
13677
  lastMsg?.toolCalls?.length && lastMsg.toolCalls.every((tc) => tc.status !== "running")
13631
13678
  );
13632
13679
  const canAppend = Boolean(
13633
- lastMsg && lastMsg.status === "streaming" && !allToolsDone && !hasUserAfter
13680
+ lastMsg && lastMsg.status === "streaming" && (!allToolsDone || sameIteration) && !hasUserAfter && matchesIteration
13634
13681
  );
13635
13682
  if (canAppend && lastMsg) {
13636
13683
  messages[lastIdx] = {
13637
13684
  ...lastMsg,
13638
13685
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
13639
13686
  reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
13687
+ reasoningTokens: reasoningTokens ?? lastMsg.reasoningTokens,
13688
+ reasoningDeltaCount: deltaReasoning ? (lastMsg.reasoningDeltaCount ?? 0) + 1 : lastMsg.reasoningDeltaCount,
13640
13689
  turnId: turnId ?? lastMsg.turnId,
13641
13690
  iterationIndex: iterationIndex ?? lastMsg.iterationIndex
13642
13691
  };
@@ -13646,6 +13695,8 @@ function applyLlmDelta(state, event) {
13646
13695
  role: "assistant",
13647
13696
  content: deltaContent,
13648
13697
  reasoning: deltaReasoning || void 0,
13698
+ reasoningTokens,
13699
+ reasoningDeltaCount: deltaReasoning ? 1 : void 0,
13649
13700
  createdAt: /* @__PURE__ */ new Date(),
13650
13701
  status: "streaming",
13651
13702
  turnId,
@@ -13721,6 +13772,11 @@ function applyLlmResponse(state, event) {
13721
13772
  }
13722
13773
  const inputTokens = numberValue(event.data.input_tokens);
13723
13774
  const outputTokens = numberValue(event.data.output_tokens);
13775
+ const reasoningTokens = numberValue(event.data.reasoning_tokens);
13776
+ if (reasoningTokens > 0) {
13777
+ const responseIdx = findLastAssistantIndex(messages);
13778
+ messages[responseIdx] = { ...messages[responseIdx], reasoningTokens };
13779
+ }
13724
13780
  return {
13725
13781
  ...state,
13726
13782
  messages,
@@ -13729,7 +13785,7 @@ function applyLlmResponse(state, event) {
13729
13785
  tokenUsage: {
13730
13786
  inputTokens,
13731
13787
  outputTokens,
13732
- reasoningTokens: numberValue(event.data.reasoning_tokens),
13788
+ reasoningTokens,
13733
13789
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
13734
13790
  totalTokens: inputTokens + outputTokens,
13735
13791
  contextWindow: numberValue(event.data.context_window),
@@ -13748,6 +13804,8 @@ ${addition}`;
13748
13804
  }
13749
13805
  function applyLlmThinking(state, event) {
13750
13806
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
13807
+ const reasoningTokens = optionalNumberValue(event.data.reasoning_tokens);
13808
+ const reasoningDeltaCount = optionalNumberValue(event.data.reasoning_delta_count);
13751
13809
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13752
13810
  const messages = [...state.messages];
13753
13811
  const idx = findLastAssistantIndex(messages);
@@ -13756,12 +13814,16 @@ function applyLlmThinking(state, event) {
13756
13814
  prev?.toolCalls?.length && prev.toolCalls.every((tc) => tc.status !== "running")
13757
13815
  );
13758
13816
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
13759
- if (!prev || allToolsDone || hasUserAfter) {
13817
+ const sameIteration = turnId !== void 0 && iterationIndex !== void 0 && (prev?.turnId === turnId && prev.iterationIndex === iterationIndex || prev?.turnId === void 0 && prev?.iterationIndex === void 0 && prev?.llmResponseEventId === void 0);
13818
+ const differentIteration = !!prev && (turnId !== void 0 && prev.turnId !== void 0 && turnId !== prev.turnId || iterationIndex !== void 0 && prev.iterationIndex !== void 0 && iterationIndex !== prev.iterationIndex);
13819
+ if (!prev || allToolsDone && !sameIteration || hasUserAfter || differentIteration) {
13760
13820
  messages.push({
13761
13821
  id: `evt-${event.eventId}`,
13762
13822
  role: "assistant",
13763
13823
  content: "",
13764
13824
  reasoning: reasoningText,
13825
+ reasoningTokens: reasoningTokens ?? void 0,
13826
+ reasoningDeltaCount: reasoningDeltaCount ?? void 0,
13765
13827
  createdAt: /* @__PURE__ */ new Date(),
13766
13828
  status: "streaming",
13767
13829
  turnId,
@@ -13771,6 +13833,8 @@ function applyLlmThinking(state, event) {
13771
13833
  messages[idx] = {
13772
13834
  ...prev,
13773
13835
  reasoning: appendText(prev.reasoning, reasoningText),
13836
+ reasoningTokens: reasoningTokens ?? prev.reasoningTokens,
13837
+ reasoningDeltaCount: reasoningDeltaCount ?? prev.reasoningDeltaCount,
13774
13838
  turnId: turnId ?? prev.turnId,
13775
13839
  iterationIndex: iterationIndex ?? prev.iterationIndex
13776
13840
  };