@invergent/agent-chat-react 2.15.9 → 2.16.1

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
@@ -5516,7 +5516,11 @@ function cancelledToolLabel(toolName) {
5516
5516
  // Arbor research-mission tools (the /auto-research coordinator loop).
5517
5517
  idea_tree: "Idea tree",
5518
5518
  dispatch_experiments: "Dispatch experiments",
5519
- merge_experiment: "Merge experiment"
5519
+ merge_experiment: "Merge experiment",
5520
+ kb_search_pages: "Knowledge base search",
5521
+ kb_list_pages: "Knowledge base index",
5522
+ kb_read_page: "Knowledge base page",
5523
+ vision_analyze: "Image analysis"
5520
5524
  };
5521
5525
  if (map[toolName]) return map[toolName];
5522
5526
  if (toolName.startsWith("mcp__")) return formatMcpToolLabel(toolName);
@@ -5579,16 +5583,27 @@ function extractToolDetail(tc) {
5579
5583
  return code3 ? truncate3(code3.split("\n")[0] ?? "", 40) : null;
5580
5584
  }
5581
5585
  case "web_search":
5582
- case "web_crawl":
5586
+ case "session_search":
5583
5587
  return stringArg("query");
5588
+ // web_crawl takes a start ``url``, not a query.
5589
+ case "web_crawl":
5584
5590
  case "web_extract": {
5585
5591
  const url = stringArg("url");
5586
- if (!url) return null;
5587
- try {
5588
- return new URL(url).hostname.replace(/^www\./, "");
5589
- } catch {
5590
- return truncate3(url, 40);
5591
- }
5592
+ return url ? hostOf2(url) : null;
5593
+ }
5594
+ case "kb_search_pages": {
5595
+ const query = stringArg("query");
5596
+ return query ? truncate3(query, 60) : null;
5597
+ }
5598
+ case "kb_read_page": {
5599
+ const path = stringArg("path");
5600
+ if (!path) return null;
5601
+ const pages = stringArg("pages");
5602
+ return pages ? `${kbPageName(path)} \xB7 pages ${pages}` : kbPageName(path);
5603
+ }
5604
+ case "vision_analyze": {
5605
+ const image = stringArg("image");
5606
+ return image && !image.startsWith("data:") ? lastPathSegment(image) : null;
5592
5607
  }
5593
5608
  case "skill_view":
5594
5609
  case "skill_manage":
@@ -5621,14 +5636,7 @@ function extractToolDetail(tc) {
5621
5636
  const title = stringArg("title");
5622
5637
  if (title) return truncate3(title, 60);
5623
5638
  const url = stringArg("url");
5624
- if (url) {
5625
- try {
5626
- return new URL(url).hostname.replace(/^www\./, "");
5627
- } catch {
5628
- return truncate3(url, 40);
5629
- }
5630
- }
5631
- return null;
5639
+ return url ? hostOf2(url) : null;
5632
5640
  }
5633
5641
  if (action === "retrieve") {
5634
5642
  return stringArg("query");
@@ -5661,6 +5669,34 @@ function lastPathSegment(path) {
5661
5669
  function truncate3(s, max) {
5662
5670
  return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
5663
5671
  }
5672
+ function hostOf2(url) {
5673
+ try {
5674
+ return new URL(url).hostname.replace(/^www\./, "") || truncate3(url, 40);
5675
+ } catch {
5676
+ return truncate3(url, 40);
5677
+ }
5678
+ }
5679
+ function kbPageName(path) {
5680
+ return lastPathSegment(path).replace(/\.(md|json)$/, "");
5681
+ }
5682
+ var _PLURAL_LABEL = {
5683
+ read_file: (n) => `Read ${n} files`,
5684
+ write_file: (n) => `Wrote ${n} files`,
5685
+ patch: (n) => `Edited ${n} files`,
5686
+ search_files: (n) => `Ran ${n} file searches`,
5687
+ terminal: (n) => `Ran ${n} commands`,
5688
+ web_search: (n) => `Ran ${n} web searches`,
5689
+ web_extract: (n) => `Fetched ${n} pages`,
5690
+ session_search: (n) => `Ran ${n} session searches`,
5691
+ kb_search_pages: (n) => `Ran ${n} knowledge base searches`,
5692
+ kb_read_page: (n) => `Read ${n} knowledge base pages`,
5693
+ vision_analyze: (n) => `Looked at ${n} images`,
5694
+ consult_expert: (n) => `Consulted ${n} experts`,
5695
+ delegate_task: (n) => `Delegated ${n} tasks`
5696
+ };
5697
+ function sameToolGroupLabel(toolName, count) {
5698
+ return _PLURAL_LABEL[toolName]?.(count) ?? `${cancelledToolLabel(toolName)} \xD7 ${count}`;
5699
+ }
5664
5700
  function toolRowLabel(tc) {
5665
5701
  const detail = extractToolDetail(tc);
5666
5702
  switch (tc.toolName) {
@@ -5684,7 +5720,22 @@ function toolRowLabel(tc) {
5684
5720
  case "web_search":
5685
5721
  return detail ? `Searched the web for "${detail}"` : "Searched the web";
5686
5722
  case "web_crawl":
5687
- return detail ? `Crawled "${detail}"` : "Crawled the web";
5723
+ return detail ? `Crawled ${detail}` : "Crawled a site";
5724
+ case "kb_search_pages": {
5725
+ if (!detail) return "Searched the knowledge base";
5726
+ return stringField(parseArgs(tc.args), "mode") === "documents" ? `Looked up document "${detail}"` : `Searched the knowledge base for "${detail}"`;
5727
+ }
5728
+ case "kb_list_pages":
5729
+ return "Browsed the knowledge base index";
5730
+ case "kb_read_page": {
5731
+ const args = parseArgs(tc.args);
5732
+ const path = stringField(args, "path");
5733
+ if (!path) return "Read a knowledge base page";
5734
+ const pages = stringField(args, "pages");
5735
+ return pages ? `Read ${kbPageName(path)}, pages ${pages}` : `Read ${kbPageName(path)}`;
5736
+ }
5737
+ case "vision_analyze":
5738
+ return detail ? `Looked at ${detail}` : "Looked at an image";
5688
5739
  case "web_extract":
5689
5740
  return detail ? `Fetched ${detail}` : "Fetched a page";
5690
5741
  case "session_search":
@@ -10882,6 +10933,17 @@ function useSmoothStream(target, isStreaming) {
10882
10933
  return displayedLen >= target.length ? target : target.slice(0, displayedLen);
10883
10934
  }
10884
10935
 
10936
+ // src/runtime/reasoning-tokens.ts
10937
+ function formatReasoningTokens(tokens) {
10938
+ if (tokens < 1e3) return String(tokens);
10939
+ return `${(tokens / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
10940
+ }
10941
+ function reasoningTokenLabel(message, live) {
10942
+ const tokens = message.reasoningTokens ?? message.reasoningDeltaCount;
10943
+ if (tokens === void 0) return live ? "Thinking..." : "Thought";
10944
+ return `${live ? "Thinking..." : "Thought \xB7"} ${formatReasoningTokens(tokens)} tokens`;
10945
+ }
10946
+
10885
10947
  // src/lib/next-action.ts
10886
10948
  var _NEXT_ACTION_RE = /<next_action([^>]*)>([\s\S]*?)<\/next_action>\s*/gi;
10887
10949
  var _PARTIAL_NEXT_ACTION_FOOTER_RE = /(?:^|\n)\s*<(?:n(?:e(?:x(?:t(?:_(?:a(?:c(?:t(?:i(?:o(?:n[\s\S]*)?)?)?)?)?)?)?)?)?)?)?$/i;
@@ -12452,7 +12514,7 @@ function SimpleFinalAnswer({
12452
12514
  function deriveIterationLabel(message) {
12453
12515
  const calls = visibleToolCalls(message);
12454
12516
  if (calls.length === 0) {
12455
- return message.reasoning ? "Thought through the problem" : null;
12517
+ return message.reasoning || message.reasoningTokens ? reasoningTokenLabel(message, false) : null;
12456
12518
  }
12457
12519
  if (calls.every((tc) => tc.toolName === "skill_view")) {
12458
12520
  return skillViewLabel(calls);
@@ -12464,8 +12526,7 @@ function deriveIterationLabel(message) {
12464
12526
  const firstName = calls[0].toolName;
12465
12527
  const allSame = calls.every((tc) => tc.toolName === firstName);
12466
12528
  if (allSame) {
12467
- const human = cancelledToolLabel(firstName);
12468
- return `${human} \xD7 ${calls.length}`;
12529
+ return sameToolGroupLabel(firstName, calls.length);
12469
12530
  }
12470
12531
  return `Used ${calls.length} tools`;
12471
12532
  }
@@ -12484,11 +12545,28 @@ function isIterationLive(message) {
12484
12545
  }
12485
12546
  return message.status === "streaming";
12486
12547
  }
12548
+ function hasInlineRunningIndicator(messages, viewMode) {
12549
+ for (let i = messages.length - 1; i >= 0; i--) {
12550
+ const message = messages[i];
12551
+ if (message.role === "user") return false;
12552
+ if (message.role !== "assistant") continue;
12553
+ if (viewMode === "simple") {
12554
+ if (message.toolCalls?.some((tc) => tc.toolName === "ask_user_question")) {
12555
+ return false;
12556
+ }
12557
+ return isIterationLive(message) && (!!message.toolCalls?.length || !message.content || message.content === message.reasoning);
12558
+ }
12559
+ return messageToEntries(message, i === messages.length - 1, {}).some(
12560
+ (entry) => entry.kind === "reasoning" && entry.isStreaming
12561
+ );
12562
+ }
12563
+ return false;
12564
+ }
12487
12565
  function liveIterationLabel(message) {
12488
12566
  const running = (message.toolCalls ?? []).filter(
12489
12567
  (tc) => tc.status === "running" && !isHiddenSimpleTool(tc)
12490
12568
  );
12491
- if (running.length === 0) return "Thinking\u2026";
12569
+ if (running.length === 0) return reasoningTokenLabel(message, true);
12492
12570
  if (running.every((tc) => tc.toolName === "skill_view")) {
12493
12571
  return `${skillViewLabel(running)}\u2026`;
12494
12572
  }
@@ -12528,7 +12606,11 @@ var _TOOL_ROW_ICON = {
12528
12606
  research_memory: lucideReact.BookOpenIcon,
12529
12607
  research_outline: lucideReact.ListIcon,
12530
12608
  run_coding_agent: lucideReact.BotIcon,
12531
- code_run: lucideReact.BotIcon
12609
+ code_run: lucideReact.BotIcon,
12610
+ kb_search_pages: lucideReact.LibraryIcon,
12611
+ kb_list_pages: lucideReact.LibraryIcon,
12612
+ kb_read_page: lucideReact.LibraryIcon,
12613
+ vision_analyze: lucideReact.EyeIcon
12532
12614
  };
12533
12615
  function _toolRowIcon(toolName) {
12534
12616
  return _TOOL_ROW_ICON[toolName] ?? lucideReact.WrenchIcon;
@@ -12545,44 +12627,16 @@ function SimpleDetailRow({ icon: Icon, children }) {
12545
12627
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
12546
12628
  ] });
12547
12629
  }
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
12630
  function IterationExpanded({
12577
12631
  message
12578
12632
  }) {
12579
12633
  const calls = visibleToolCalls(message);
12580
- const reasoning = (message.reasoning ?? "").trim();
12634
+ const hasReasoning = !!message.reasoning || !!message.reasoningTokens;
12581
12635
  const anyVisibleRunning = calls.some((tc) => tc.status === "running");
12582
- const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
12583
- if (!reasoning && calls.length === 0) return null;
12636
+ const showDone = !anyVisibleRunning && (calls.length > 0 || hasReasoning);
12637
+ if (!hasReasoning && calls.length === 0) return null;
12584
12638
  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 }) }),
12639
+ hasReasoning && /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: lucideReact.ClockIcon, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: reasoningTokenLabel(message, false) }) }),
12586
12640
  calls.map((tc) => {
12587
12641
  if (tc.toolName === "todo") {
12588
12642
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsxRuntime.jsx(TodoToolBlock, { tc }) }, tc.id);
@@ -12597,6 +12651,7 @@ function IterationExpanded({
12597
12651
  }
12598
12652
  function IterationGroup({
12599
12653
  message,
12654
+ isActive = true,
12600
12655
  sessionId,
12601
12656
  artifactFallbacks,
12602
12657
  onFileSelect
@@ -12616,7 +12671,7 @@ function IterationGroup({
12616
12671
  }
12617
12672
  ) });
12618
12673
  }
12619
- if (isIterationLive(message)) {
12674
+ if (isActive && isIterationLive(message)) {
12620
12675
  return /* @__PURE__ */ jsxRuntime.jsx(
12621
12676
  OrbShimmerLabel,
12622
12677
  {
@@ -12630,6 +12685,9 @@ function IterationGroup({
12630
12685
  if (!label) {
12631
12686
  return null;
12632
12687
  }
12688
+ if (!summary && visibleToolCalls(message).length === 0) {
12689
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-0.5 text-sm text-muted-foreground", children: label });
12690
+ }
12633
12691
  const labelTone = summary ? "text-muted-foreground italic" : "text-muted-foreground";
12634
12692
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
12635
12693
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -12723,6 +12781,7 @@ function LoopResultAffordance({ message }) {
12723
12781
  }
12724
12782
  function SimpleAssistantGroup({
12725
12783
  messages,
12784
+ activeAssistantId,
12726
12785
  isRunning,
12727
12786
  sessionId,
12728
12787
  artifactFallbacks,
@@ -12734,7 +12793,7 @@ function SimpleAssistantGroup({
12734
12793
  const tail = assistantMessages[assistantMessages.length - 1];
12735
12794
  const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
12736
12795
  const tailIsTextOnly = !!tail && !tailHasTools;
12737
- const finalText = tailIsTextOnly && tail.content ? tail.content : "";
12796
+ const finalText = tailIsTextOnly && tail.content !== tail.reasoning ? tail.content : "";
12738
12797
  const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
12739
12798
  const turnComplete = !isRunning && tail?.status === "complete";
12740
12799
  const summaryPref = finalText !== null ? stripAndParseNextAction(finalText).action?.summary ?? "hide" : "hide";
@@ -12771,7 +12830,7 @@ function SimpleAssistantGroup({
12771
12830
  );
12772
12831
  }
12773
12832
  if (message === tail && tailIsTextOnly && finalText) return null;
12774
- const rawContent = (message.content ?? "").trim();
12833
+ const rawContent = message.content === message.reasoning ? "" : (message.content ?? "").trim();
12775
12834
  const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
12776
12835
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
12777
12836
  const askBody = hasUserFacingAskContent(message) ? cleaned : null;
@@ -12781,6 +12840,7 @@ function SimpleAssistantGroup({
12781
12840
  IterationGroup,
12782
12841
  {
12783
12842
  message,
12843
+ isActive: message.id === activeAssistantId,
12784
12844
  sessionId,
12785
12845
  artifactFallbacks,
12786
12846
  onFileSelect
@@ -12813,6 +12873,7 @@ function SimpleAssistantGroup({
12813
12873
  }
12814
12874
  function AssistantGroup({
12815
12875
  messages,
12876
+ activeAssistantId,
12816
12877
  lastGlobalIndex,
12817
12878
  totalMessages,
12818
12879
  isRunning,
@@ -12828,6 +12889,7 @@ function AssistantGroup({
12828
12889
  SimpleAssistantGroup,
12829
12890
  {
12830
12891
  messages,
12892
+ activeAssistantId,
12831
12893
  isRunning,
12832
12894
  sessionId,
12833
12895
  artifactFallbacks,
@@ -12959,6 +13021,15 @@ function ChatThread({
12959
13021
  onOpenIntegrations
12960
13022
  }) {
12961
13023
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
13024
+ const activeAssistantId = react.useMemo(() => {
13025
+ if (!isRunning) return void 0;
13026
+ for (let i = messages.length - 1; i >= 0; i--) {
13027
+ const message = messages[i];
13028
+ if (message.role === "user") return void 0;
13029
+ if (message.role === "assistant") return message.id;
13030
+ }
13031
+ return void 0;
13032
+ }, [messages, isRunning]);
12962
13033
  const pendingAsk = react.useMemo(() => pendingAskCall(messages), [messages]);
12963
13034
  const askPolicy = react.useMemo(
12964
13035
  () => askComposerPolicy(pendingAsk),
@@ -12966,13 +13037,26 @@ function ChatThread({
12966
13037
  );
12967
13038
  const awaitingInput = askPolicy !== null;
12968
13039
  const orbActivity = react.useMemo(
12969
- () => deriveOrbActivity(
12970
- messages,
12971
- viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12972
- ),
13040
+ () => {
13041
+ const activity = deriveOrbActivity(
13042
+ messages,
13043
+ viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
13044
+ );
13045
+ if (viewMode === "simple" && activity.state === "solving") {
13046
+ const latest = [...messages].reverse().find((message) => message.role === "assistant");
13047
+ if (latest && !latest.toolCalls?.some((tc) => tc.status === "running")) {
13048
+ return { ...activity, label: reasoningTokenLabel(latest, true) };
13049
+ }
13050
+ }
13051
+ return activity;
13052
+ },
12973
13053
  [messages, viewMode]
12974
13054
  );
12975
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
13055
+ const inlineRunningIndicator = react.useMemo(
13056
+ () => hasInlineRunningIndicator(messages, viewMode),
13057
+ [messages, viewMode]
13058
+ );
13059
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput && !inlineRunningIndicator;
12976
13060
  const composerLocked = askPolicy?.locked ?? false;
12977
13061
  const composerDisabled = disabled || composerLocked;
12978
13062
  const composerDisabledReason = composerLocked ? askPolicy?.reason : disabledReason;
@@ -13158,6 +13242,7 @@ function ChatThread({
13158
13242
  AssistantGroup,
13159
13243
  {
13160
13244
  messages: group.messages,
13245
+ activeAssistantId,
13161
13246
  lastGlobalIndex: group.lastGlobalIndex,
13162
13247
  totalMessages: messages.length,
13163
13248
  isRunning,
@@ -13403,8 +13488,17 @@ function applyAgentChatEvent(state, event) {
13403
13488
  return collectResearchSource(withResult, toolName, event.data);
13404
13489
  }
13405
13490
  case "harness.wake":
13406
- case "llm.request":
13407
13491
  return { ...nextState, terminal: false, isRunning: true };
13492
+ case "llm.request":
13493
+ return {
13494
+ ...nextState,
13495
+ terminal: false,
13496
+ isRunning: true,
13497
+ hadDeltas: false,
13498
+ messages: nextState.messages.map(
13499
+ (message) => message.role === "assistant" && message.status === "streaming" && !message.toolCalls?.some((tc) => tc.status === "running") ? { ...message, status: "complete" } : message
13500
+ )
13501
+ };
13408
13502
  case "harness.crash":
13409
13503
  case "stream.timeout":
13410
13504
  return nextState;
@@ -13621,22 +13715,31 @@ function browserMarker(event) {
13621
13715
  function applyLlmDelta(state, event) {
13622
13716
  const deltaContent = stringValue2(event.data.content);
13623
13717
  const deltaReasoning = stringValue2(event.data.reasoning);
13718
+ 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
13719
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13625
13720
  const messages = [...state.messages];
13626
13721
  const lastIdx = findLastAssistantIndex(messages);
13627
13722
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
13628
13723
  const hasUserAfter = lastIdx >= 0 && hasUserAfterIndex(messages, lastIdx);
13724
+ const matchesIteration = (turnId === void 0 || lastMsg?.turnId === turnId) && (iterationIndex === void 0 || lastMsg?.iterationIndex === iterationIndex);
13725
+ const sameIteration = turnId !== void 0 && iterationIndex !== void 0 && matchesIteration;
13726
+ if (reasoningTokens !== void 0 && !deltaContent && !deltaReasoning && lastMsg && !hasUserAfter && matchesIteration) {
13727
+ messages[lastIdx] = { ...lastMsg, reasoningTokens };
13728
+ return { ...state, messages };
13729
+ }
13629
13730
  const allToolsDone = Boolean(
13630
13731
  lastMsg?.toolCalls?.length && lastMsg.toolCalls.every((tc) => tc.status !== "running")
13631
13732
  );
13632
13733
  const canAppend = Boolean(
13633
- lastMsg && lastMsg.status === "streaming" && !allToolsDone && !hasUserAfter
13734
+ lastMsg && lastMsg.status === "streaming" && (!allToolsDone || sameIteration) && !hasUserAfter && matchesIteration
13634
13735
  );
13635
13736
  if (canAppend && lastMsg) {
13636
13737
  messages[lastIdx] = {
13637
13738
  ...lastMsg,
13638
13739
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
13639
13740
  reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
13741
+ reasoningTokens: reasoningTokens ?? lastMsg.reasoningTokens,
13742
+ reasoningDeltaCount: deltaReasoning ? (lastMsg.reasoningDeltaCount ?? 0) + 1 : lastMsg.reasoningDeltaCount,
13640
13743
  turnId: turnId ?? lastMsg.turnId,
13641
13744
  iterationIndex: iterationIndex ?? lastMsg.iterationIndex
13642
13745
  };
@@ -13646,6 +13749,8 @@ function applyLlmDelta(state, event) {
13646
13749
  role: "assistant",
13647
13750
  content: deltaContent,
13648
13751
  reasoning: deltaReasoning || void 0,
13752
+ reasoningTokens,
13753
+ reasoningDeltaCount: deltaReasoning ? 1 : void 0,
13649
13754
  createdAt: /* @__PURE__ */ new Date(),
13650
13755
  status: "streaming",
13651
13756
  turnId,
@@ -13721,6 +13826,11 @@ function applyLlmResponse(state, event) {
13721
13826
  }
13722
13827
  const inputTokens = numberValue(event.data.input_tokens);
13723
13828
  const outputTokens = numberValue(event.data.output_tokens);
13829
+ const reasoningTokens = numberValue(event.data.reasoning_tokens);
13830
+ if (reasoningTokens > 0) {
13831
+ const responseIdx = findLastAssistantIndex(messages);
13832
+ messages[responseIdx] = { ...messages[responseIdx], reasoningTokens };
13833
+ }
13724
13834
  return {
13725
13835
  ...state,
13726
13836
  messages,
@@ -13729,7 +13839,7 @@ function applyLlmResponse(state, event) {
13729
13839
  tokenUsage: {
13730
13840
  inputTokens,
13731
13841
  outputTokens,
13732
- reasoningTokens: numberValue(event.data.reasoning_tokens),
13842
+ reasoningTokens,
13733
13843
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
13734
13844
  totalTokens: inputTokens + outputTokens,
13735
13845
  contextWindow: numberValue(event.data.context_window),
@@ -13748,6 +13858,8 @@ ${addition}`;
13748
13858
  }
13749
13859
  function applyLlmThinking(state, event) {
13750
13860
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
13861
+ const reasoningTokens = optionalNumberValue(event.data.reasoning_tokens);
13862
+ const reasoningDeltaCount = optionalNumberValue(event.data.reasoning_delta_count);
13751
13863
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13752
13864
  const messages = [...state.messages];
13753
13865
  const idx = findLastAssistantIndex(messages);
@@ -13756,12 +13868,16 @@ function applyLlmThinking(state, event) {
13756
13868
  prev?.toolCalls?.length && prev.toolCalls.every((tc) => tc.status !== "running")
13757
13869
  );
13758
13870
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
13759
- if (!prev || allToolsDone || hasUserAfter) {
13871
+ 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);
13872
+ const differentIteration = !!prev && (turnId !== void 0 && prev.turnId !== void 0 && turnId !== prev.turnId || iterationIndex !== void 0 && prev.iterationIndex !== void 0 && iterationIndex !== prev.iterationIndex);
13873
+ if (!prev || allToolsDone && !sameIteration || hasUserAfter || differentIteration) {
13760
13874
  messages.push({
13761
13875
  id: `evt-${event.eventId}`,
13762
13876
  role: "assistant",
13763
13877
  content: "",
13764
13878
  reasoning: reasoningText,
13879
+ reasoningTokens: reasoningTokens ?? void 0,
13880
+ reasoningDeltaCount: reasoningDeltaCount ?? void 0,
13765
13881
  createdAt: /* @__PURE__ */ new Date(),
13766
13882
  status: "streaming",
13767
13883
  turnId,
@@ -13771,6 +13887,8 @@ function applyLlmThinking(state, event) {
13771
13887
  messages[idx] = {
13772
13888
  ...prev,
13773
13889
  reasoning: appendText(prev.reasoning, reasoningText),
13890
+ reasoningTokens: reasoningTokens ?? prev.reasoningTokens,
13891
+ reasoningDeltaCount: reasoningDeltaCount ?? prev.reasoningDeltaCount,
13774
13892
  turnId: turnId ?? prev.turnId,
13775
13893
  iterationIndex: iterationIndex ?? prev.iterationIndex
13776
13894
  };