@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.d.cts CHANGED
@@ -61,6 +61,10 @@ interface AgentChatMessage {
61
61
  status: AgentChatMessageStatus;
62
62
  toolCalls?: AgentChatToolCallInfo[];
63
63
  reasoning?: string;
64
+ /** Provider-reported reasoning usage for this iteration, when available. */
65
+ reasoningTokens?: number;
66
+ /** Incoming reasoning chunks counted live, before provider usage arrives. */
67
+ reasoningDeltaCount?: number;
64
68
  systemKind?: AgentChatSystemKind;
65
69
  systemMeta?: Record<string, unknown>;
66
70
  errorInfo?: AgentChatErrorInfo;
package/dist/index.d.ts CHANGED
@@ -61,6 +61,10 @@ interface AgentChatMessage {
61
61
  status: AgentChatMessageStatus;
62
62
  toolCalls?: AgentChatToolCallInfo[];
63
63
  reasoning?: string;
64
+ /** Provider-reported reasoning usage for this iteration, when available. */
65
+ reasoningTokens?: number;
66
+ /** Incoming reasoning chunks counted live, before provider usage arrives. */
67
+ reasoningDeltaCount?: number;
64
68
  systemKind?: AgentChatSystemKind;
65
69
  systemMeta?: Record<string, unknown>;
66
70
  errorInfo?: AgentChatErrorInfo;
package/dist/index.js CHANGED
@@ -5255,7 +5255,11 @@ function cancelledToolLabel(toolName) {
5255
5255
  // Arbor research-mission tools (the /auto-research coordinator loop).
5256
5256
  idea_tree: "Idea tree",
5257
5257
  dispatch_experiments: "Dispatch experiments",
5258
- merge_experiment: "Merge experiment"
5258
+ merge_experiment: "Merge experiment",
5259
+ kb_search_pages: "Knowledge base search",
5260
+ kb_list_pages: "Knowledge base index",
5261
+ kb_read_page: "Knowledge base page",
5262
+ vision_analyze: "Image analysis"
5259
5263
  };
5260
5264
  if (map[toolName]) return map[toolName];
5261
5265
  if (toolName.startsWith("mcp__")) return formatMcpToolLabel(toolName);
@@ -5318,16 +5322,27 @@ function extractToolDetail(tc) {
5318
5322
  return code3 ? truncate3(code3.split("\n")[0] ?? "", 40) : null;
5319
5323
  }
5320
5324
  case "web_search":
5321
- case "web_crawl":
5325
+ case "session_search":
5322
5326
  return stringArg("query");
5327
+ // web_crawl takes a start ``url``, not a query.
5328
+ case "web_crawl":
5323
5329
  case "web_extract": {
5324
5330
  const url = stringArg("url");
5325
- if (!url) return null;
5326
- try {
5327
- return new URL(url).hostname.replace(/^www\./, "");
5328
- } catch {
5329
- return truncate3(url, 40);
5330
- }
5331
+ return url ? hostOf2(url) : null;
5332
+ }
5333
+ case "kb_search_pages": {
5334
+ const query = stringArg("query");
5335
+ return query ? truncate3(query, 60) : null;
5336
+ }
5337
+ case "kb_read_page": {
5338
+ const path = stringArg("path");
5339
+ if (!path) return null;
5340
+ const pages = stringArg("pages");
5341
+ return pages ? `${kbPageName(path)} \xB7 pages ${pages}` : kbPageName(path);
5342
+ }
5343
+ case "vision_analyze": {
5344
+ const image = stringArg("image");
5345
+ return image && !image.startsWith("data:") ? lastPathSegment(image) : null;
5331
5346
  }
5332
5347
  case "skill_view":
5333
5348
  case "skill_manage":
@@ -5360,14 +5375,7 @@ function extractToolDetail(tc) {
5360
5375
  const title = stringArg("title");
5361
5376
  if (title) return truncate3(title, 60);
5362
5377
  const url = stringArg("url");
5363
- if (url) {
5364
- try {
5365
- return new URL(url).hostname.replace(/^www\./, "");
5366
- } catch {
5367
- return truncate3(url, 40);
5368
- }
5369
- }
5370
- return null;
5378
+ return url ? hostOf2(url) : null;
5371
5379
  }
5372
5380
  if (action === "retrieve") {
5373
5381
  return stringArg("query");
@@ -5400,6 +5408,34 @@ function lastPathSegment(path) {
5400
5408
  function truncate3(s, max) {
5401
5409
  return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
5402
5410
  }
5411
+ function hostOf2(url) {
5412
+ try {
5413
+ return new URL(url).hostname.replace(/^www\./, "") || truncate3(url, 40);
5414
+ } catch {
5415
+ return truncate3(url, 40);
5416
+ }
5417
+ }
5418
+ function kbPageName(path) {
5419
+ return lastPathSegment(path).replace(/\.(md|json)$/, "");
5420
+ }
5421
+ var _PLURAL_LABEL = {
5422
+ read_file: (n) => `Read ${n} files`,
5423
+ write_file: (n) => `Wrote ${n} files`,
5424
+ patch: (n) => `Edited ${n} files`,
5425
+ search_files: (n) => `Ran ${n} file searches`,
5426
+ terminal: (n) => `Ran ${n} commands`,
5427
+ web_search: (n) => `Ran ${n} web searches`,
5428
+ web_extract: (n) => `Fetched ${n} pages`,
5429
+ session_search: (n) => `Ran ${n} session searches`,
5430
+ kb_search_pages: (n) => `Ran ${n} knowledge base searches`,
5431
+ kb_read_page: (n) => `Read ${n} knowledge base pages`,
5432
+ vision_analyze: (n) => `Looked at ${n} images`,
5433
+ consult_expert: (n) => `Consulted ${n} experts`,
5434
+ delegate_task: (n) => `Delegated ${n} tasks`
5435
+ };
5436
+ function sameToolGroupLabel(toolName, count) {
5437
+ return _PLURAL_LABEL[toolName]?.(count) ?? `${cancelledToolLabel(toolName)} \xD7 ${count}`;
5438
+ }
5403
5439
  function toolRowLabel(tc) {
5404
5440
  const detail = extractToolDetail(tc);
5405
5441
  switch (tc.toolName) {
@@ -5423,7 +5459,22 @@ function toolRowLabel(tc) {
5423
5459
  case "web_search":
5424
5460
  return detail ? `Searched the web for "${detail}"` : "Searched the web";
5425
5461
  case "web_crawl":
5426
- return detail ? `Crawled "${detail}"` : "Crawled the web";
5462
+ return detail ? `Crawled ${detail}` : "Crawled a site";
5463
+ case "kb_search_pages": {
5464
+ if (!detail) return "Searched the knowledge base";
5465
+ return stringField(parseArgs(tc.args), "mode") === "documents" ? `Looked up document "${detail}"` : `Searched the knowledge base for "${detail}"`;
5466
+ }
5467
+ case "kb_list_pages":
5468
+ return "Browsed the knowledge base index";
5469
+ case "kb_read_page": {
5470
+ const args = parseArgs(tc.args);
5471
+ const path = stringField(args, "path");
5472
+ if (!path) return "Read a knowledge base page";
5473
+ const pages = stringField(args, "pages");
5474
+ return pages ? `Read ${kbPageName(path)}, pages ${pages}` : `Read ${kbPageName(path)}`;
5475
+ }
5476
+ case "vision_analyze":
5477
+ return detail ? `Looked at ${detail}` : "Looked at an image";
5427
5478
  case "web_extract":
5428
5479
  return detail ? `Fetched ${detail}` : "Fetched a page";
5429
5480
  case "session_search":
@@ -10578,6 +10629,17 @@ function useSmoothStream(target, isStreaming) {
10578
10629
  return displayedLen >= target.length ? target : target.slice(0, displayedLen);
10579
10630
  }
10580
10631
 
10632
+ // src/runtime/reasoning-tokens.ts
10633
+ function formatReasoningTokens(tokens) {
10634
+ if (tokens < 1e3) return String(tokens);
10635
+ return `${(tokens / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
10636
+ }
10637
+ function reasoningTokenLabel(message, live) {
10638
+ const tokens = message.reasoningTokens ?? message.reasoningDeltaCount;
10639
+ if (tokens === void 0) return live ? "Thinking..." : "Thought";
10640
+ return `${live ? "Thinking..." : "Thought \xB7"} ${formatReasoningTokens(tokens)} tokens`;
10641
+ }
10642
+
10581
10643
  // src/lib/next-action.ts
10582
10644
  var _NEXT_ACTION_RE = /<next_action([^>]*)>([\s\S]*?)<\/next_action>\s*/gi;
10583
10645
  var _PARTIAL_NEXT_ACTION_FOOTER_RE = /(?:^|\n)\s*<(?:n(?:e(?:x(?:t(?:_(?:a(?:c(?:t(?:i(?:o(?:n[\s\S]*)?)?)?)?)?)?)?)?)?)?)?$/i;
@@ -12133,7 +12195,7 @@ function SimpleFinalAnswer({
12133
12195
  function deriveIterationLabel(message) {
12134
12196
  const calls = visibleToolCalls(message);
12135
12197
  if (calls.length === 0) {
12136
- return message.reasoning ? "Thought through the problem" : null;
12198
+ return message.reasoning || message.reasoningTokens ? reasoningTokenLabel(message, false) : null;
12137
12199
  }
12138
12200
  if (calls.every((tc) => tc.toolName === "skill_view")) {
12139
12201
  return skillViewLabel(calls);
@@ -12145,8 +12207,7 @@ function deriveIterationLabel(message) {
12145
12207
  const firstName = calls[0].toolName;
12146
12208
  const allSame = calls.every((tc) => tc.toolName === firstName);
12147
12209
  if (allSame) {
12148
- const human = cancelledToolLabel(firstName);
12149
- return `${human} \xD7 ${calls.length}`;
12210
+ return sameToolGroupLabel(firstName, calls.length);
12150
12211
  }
12151
12212
  return `Used ${calls.length} tools`;
12152
12213
  }
@@ -12165,11 +12226,28 @@ function isIterationLive(message) {
12165
12226
  }
12166
12227
  return message.status === "streaming";
12167
12228
  }
12229
+ function hasInlineRunningIndicator(messages, viewMode) {
12230
+ for (let i = messages.length - 1; i >= 0; i--) {
12231
+ const message = messages[i];
12232
+ if (message.role === "user") return false;
12233
+ if (message.role !== "assistant") continue;
12234
+ if (viewMode === "simple") {
12235
+ if (message.toolCalls?.some((tc) => tc.toolName === "ask_user_question")) {
12236
+ return false;
12237
+ }
12238
+ return isIterationLive(message) && (!!message.toolCalls?.length || !message.content || message.content === message.reasoning);
12239
+ }
12240
+ return messageToEntries(message, i === messages.length - 1, {}).some(
12241
+ (entry) => entry.kind === "reasoning" && entry.isStreaming
12242
+ );
12243
+ }
12244
+ return false;
12245
+ }
12168
12246
  function liveIterationLabel(message) {
12169
12247
  const running = (message.toolCalls ?? []).filter(
12170
12248
  (tc) => tc.status === "running" && !isHiddenSimpleTool(tc)
12171
12249
  );
12172
- if (running.length === 0) return "Thinking\u2026";
12250
+ if (running.length === 0) return reasoningTokenLabel(message, true);
12173
12251
  if (running.every((tc) => tc.toolName === "skill_view")) {
12174
12252
  return `${skillViewLabel(running)}\u2026`;
12175
12253
  }
@@ -12209,7 +12287,11 @@ var _TOOL_ROW_ICON = {
12209
12287
  research_memory: BookOpenIcon,
12210
12288
  research_outline: ListIcon,
12211
12289
  run_coding_agent: BotIcon,
12212
- code_run: BotIcon
12290
+ code_run: BotIcon,
12291
+ kb_search_pages: LibraryIcon,
12292
+ kb_list_pages: LibraryIcon,
12293
+ kb_read_page: LibraryIcon,
12294
+ vision_analyze: EyeIcon
12213
12295
  };
12214
12296
  function _toolRowIcon(toolName) {
12215
12297
  return _TOOL_ROW_ICON[toolName] ?? WrenchIcon;
@@ -12226,44 +12308,16 @@ function SimpleDetailRow({ icon: Icon, children }) {
12226
12308
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
12227
12309
  ] });
12228
12310
  }
12229
- function ClampedReasoning({
12230
- text,
12231
- previewParagraphs = 2
12232
- }) {
12233
- const paragraphs = splitReasoningParagraphs(text);
12234
- const [expanded, setExpanded] = useState(false);
12235
- const hasMore = paragraphs.length > previewParagraphs;
12236
- const visible = expanded || !hasMore ? paragraphs : paragraphs.slice(0, previewParagraphs);
12237
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
12238
- visible.map((paragraph, i) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: paragraph }, i)),
12239
- hasMore && /* @__PURE__ */ jsx(
12240
- "button",
12241
- {
12242
- type: "button",
12243
- onClick: () => setExpanded((prev) => !prev),
12244
- className: "text-xs font-medium text-foreground hover:text-foreground/70 cursor-pointer",
12245
- children: expanded ? "Show less" : "Show more..."
12246
- }
12247
- )
12248
- ] });
12249
- }
12250
- function splitReasoningParagraphs(text) {
12251
- const trimmed = text.trim();
12252
- if (!trimmed) return [];
12253
- const blankSplit = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
12254
- if (blankSplit.length > 1) return blankSplit;
12255
- return trimmed.split(/\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
12256
- }
12257
12311
  function IterationExpanded({
12258
12312
  message
12259
12313
  }) {
12260
12314
  const calls = visibleToolCalls(message);
12261
- const reasoning = (message.reasoning ?? "").trim();
12315
+ const hasReasoning = !!message.reasoning || !!message.reasoningTokens;
12262
12316
  const anyVisibleRunning = calls.some((tc) => tc.status === "running");
12263
- const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
12264
- if (!reasoning && calls.length === 0) return null;
12317
+ const showDone = !anyVisibleRunning && (calls.length > 0 || hasReasoning);
12318
+ if (!hasReasoning && calls.length === 0) return null;
12265
12319
  return /* @__PURE__ */ jsxs("div", { className: "ml-2 space-y-3 border-l border-border/40 pl-4 py-1", children: [
12266
- reasoning && /* @__PURE__ */ jsx(SimpleDetailRow, { icon: ClockIcon, children: /* @__PURE__ */ jsx(ClampedReasoning, { text: reasoning }) }),
12320
+ hasReasoning && /* @__PURE__ */ jsx(SimpleDetailRow, { icon: ClockIcon, children: /* @__PURE__ */ jsx("span", { children: reasoningTokenLabel(message, false) }) }),
12267
12321
  calls.map((tc) => {
12268
12322
  if (tc.toolName === "todo") {
12269
12323
  return /* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(TodoToolBlock, { tc }) }, tc.id);
@@ -12278,6 +12332,7 @@ function IterationExpanded({
12278
12332
  }
12279
12333
  function IterationGroup({
12280
12334
  message,
12335
+ isActive = true,
12281
12336
  sessionId,
12282
12337
  artifactFallbacks,
12283
12338
  onFileSelect
@@ -12297,7 +12352,7 @@ function IterationGroup({
12297
12352
  }
12298
12353
  ) });
12299
12354
  }
12300
- if (isIterationLive(message)) {
12355
+ if (isActive && isIterationLive(message)) {
12301
12356
  return /* @__PURE__ */ jsx(
12302
12357
  OrbShimmerLabel,
12303
12358
  {
@@ -12311,6 +12366,9 @@ function IterationGroup({
12311
12366
  if (!label) {
12312
12367
  return null;
12313
12368
  }
12369
+ if (!summary && visibleToolCalls(message).length === 0) {
12370
+ return /* @__PURE__ */ jsx("p", { className: "py-0.5 text-sm text-muted-foreground", children: label });
12371
+ }
12314
12372
  const labelTone = summary ? "text-muted-foreground italic" : "text-muted-foreground";
12315
12373
  return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
12316
12374
  /* @__PURE__ */ jsxs(
@@ -12404,6 +12462,7 @@ function LoopResultAffordance({ message }) {
12404
12462
  }
12405
12463
  function SimpleAssistantGroup({
12406
12464
  messages,
12465
+ activeAssistantId,
12407
12466
  isRunning,
12408
12467
  sessionId,
12409
12468
  artifactFallbacks,
@@ -12415,7 +12474,7 @@ function SimpleAssistantGroup({
12415
12474
  const tail = assistantMessages[assistantMessages.length - 1];
12416
12475
  const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
12417
12476
  const tailIsTextOnly = !!tail && !tailHasTools;
12418
- const finalText = tailIsTextOnly && tail.content ? tail.content : "";
12477
+ const finalText = tailIsTextOnly && tail.content !== tail.reasoning ? tail.content : "";
12419
12478
  const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
12420
12479
  const turnComplete = !isRunning && tail?.status === "complete";
12421
12480
  const summaryPref = finalText !== null ? stripAndParseNextAction(finalText).action?.summary ?? "hide" : "hide";
@@ -12452,7 +12511,7 @@ function SimpleAssistantGroup({
12452
12511
  );
12453
12512
  }
12454
12513
  if (message === tail && tailIsTextOnly && finalText) return null;
12455
- const rawContent = (message.content ?? "").trim();
12514
+ const rawContent = message.content === message.reasoning ? "" : (message.content ?? "").trim();
12456
12515
  const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
12457
12516
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
12458
12517
  const askBody = hasUserFacingAskContent(message) ? cleaned : null;
@@ -12462,6 +12521,7 @@ function SimpleAssistantGroup({
12462
12521
  IterationGroup,
12463
12522
  {
12464
12523
  message,
12524
+ isActive: message.id === activeAssistantId,
12465
12525
  sessionId,
12466
12526
  artifactFallbacks,
12467
12527
  onFileSelect
@@ -12494,6 +12554,7 @@ function SimpleAssistantGroup({
12494
12554
  }
12495
12555
  function AssistantGroup({
12496
12556
  messages,
12557
+ activeAssistantId,
12497
12558
  lastGlobalIndex,
12498
12559
  totalMessages,
12499
12560
  isRunning,
@@ -12509,6 +12570,7 @@ function AssistantGroup({
12509
12570
  SimpleAssistantGroup,
12510
12571
  {
12511
12572
  messages,
12573
+ activeAssistantId,
12512
12574
  isRunning,
12513
12575
  sessionId,
12514
12576
  artifactFallbacks,
@@ -12640,6 +12702,15 @@ function ChatThread({
12640
12702
  onOpenIntegrations
12641
12703
  }) {
12642
12704
  const groups = useMemo(() => groupMessages(messages), [messages]);
12705
+ const activeAssistantId = useMemo(() => {
12706
+ if (!isRunning) return void 0;
12707
+ for (let i = messages.length - 1; i >= 0; i--) {
12708
+ const message = messages[i];
12709
+ if (message.role === "user") return void 0;
12710
+ if (message.role === "assistant") return message.id;
12711
+ }
12712
+ return void 0;
12713
+ }, [messages, isRunning]);
12643
12714
  const pendingAsk = useMemo(() => pendingAskCall(messages), [messages]);
12644
12715
  const askPolicy = useMemo(
12645
12716
  () => askComposerPolicy(pendingAsk),
@@ -12647,13 +12718,26 @@ function ChatThread({
12647
12718
  );
12648
12719
  const awaitingInput = askPolicy !== null;
12649
12720
  const orbActivity = useMemo(
12650
- () => deriveOrbActivity(
12651
- messages,
12652
- viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12653
- ),
12721
+ () => {
12722
+ const activity = deriveOrbActivity(
12723
+ messages,
12724
+ viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12725
+ );
12726
+ if (viewMode === "simple" && activity.state === "solving") {
12727
+ const latest = [...messages].reverse().find((message) => message.role === "assistant");
12728
+ if (latest && !latest.toolCalls?.some((tc) => tc.status === "running")) {
12729
+ return { ...activity, label: reasoningTokenLabel(latest, true) };
12730
+ }
12731
+ }
12732
+ return activity;
12733
+ },
12654
12734
  [messages, viewMode]
12655
12735
  );
12656
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
12736
+ const inlineRunningIndicator = useMemo(
12737
+ () => hasInlineRunningIndicator(messages, viewMode),
12738
+ [messages, viewMode]
12739
+ );
12740
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput && !inlineRunningIndicator;
12657
12741
  const composerLocked = askPolicy?.locked ?? false;
12658
12742
  const composerDisabled = disabled || composerLocked;
12659
12743
  const composerDisabledReason = composerLocked ? askPolicy?.reason : disabledReason;
@@ -12839,6 +12923,7 @@ function ChatThread({
12839
12923
  AssistantGroup,
12840
12924
  {
12841
12925
  messages: group.messages,
12926
+ activeAssistantId,
12842
12927
  lastGlobalIndex: group.lastGlobalIndex,
12843
12928
  totalMessages: messages.length,
12844
12929
  isRunning,
@@ -13081,8 +13166,17 @@ function applyAgentChatEvent(state, event) {
13081
13166
  return collectResearchSource(withResult, toolName, event.data);
13082
13167
  }
13083
13168
  case "harness.wake":
13084
- case "llm.request":
13085
13169
  return { ...nextState, terminal: false, isRunning: true };
13170
+ case "llm.request":
13171
+ return {
13172
+ ...nextState,
13173
+ terminal: false,
13174
+ isRunning: true,
13175
+ hadDeltas: false,
13176
+ messages: nextState.messages.map(
13177
+ (message) => message.role === "assistant" && message.status === "streaming" && !message.toolCalls?.some((tc) => tc.status === "running") ? { ...message, status: "complete" } : message
13178
+ )
13179
+ };
13086
13180
  case "harness.crash":
13087
13181
  case "stream.timeout":
13088
13182
  return nextState;
@@ -13299,22 +13393,31 @@ function browserMarker(event) {
13299
13393
  function applyLlmDelta(state, event) {
13300
13394
  const deltaContent = stringValue2(event.data.content);
13301
13395
  const deltaReasoning = stringValue2(event.data.reasoning);
13396
+ 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;
13302
13397
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13303
13398
  const messages = [...state.messages];
13304
13399
  const lastIdx = findLastAssistantIndex(messages);
13305
13400
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
13306
13401
  const hasUserAfter = lastIdx >= 0 && hasUserAfterIndex(messages, lastIdx);
13402
+ const matchesIteration = (turnId === void 0 || lastMsg?.turnId === turnId) && (iterationIndex === void 0 || lastMsg?.iterationIndex === iterationIndex);
13403
+ const sameIteration = turnId !== void 0 && iterationIndex !== void 0 && matchesIteration;
13404
+ if (reasoningTokens !== void 0 && !deltaContent && !deltaReasoning && lastMsg && !hasUserAfter && matchesIteration) {
13405
+ messages[lastIdx] = { ...lastMsg, reasoningTokens };
13406
+ return { ...state, messages };
13407
+ }
13307
13408
  const allToolsDone = Boolean(
13308
13409
  lastMsg?.toolCalls?.length && lastMsg.toolCalls.every((tc) => tc.status !== "running")
13309
13410
  );
13310
13411
  const canAppend = Boolean(
13311
- lastMsg && lastMsg.status === "streaming" && !allToolsDone && !hasUserAfter
13412
+ lastMsg && lastMsg.status === "streaming" && (!allToolsDone || sameIteration) && !hasUserAfter && matchesIteration
13312
13413
  );
13313
13414
  if (canAppend && lastMsg) {
13314
13415
  messages[lastIdx] = {
13315
13416
  ...lastMsg,
13316
13417
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
13317
13418
  reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
13419
+ reasoningTokens: reasoningTokens ?? lastMsg.reasoningTokens,
13420
+ reasoningDeltaCount: deltaReasoning ? (lastMsg.reasoningDeltaCount ?? 0) + 1 : lastMsg.reasoningDeltaCount,
13318
13421
  turnId: turnId ?? lastMsg.turnId,
13319
13422
  iterationIndex: iterationIndex ?? lastMsg.iterationIndex
13320
13423
  };
@@ -13324,6 +13427,8 @@ function applyLlmDelta(state, event) {
13324
13427
  role: "assistant",
13325
13428
  content: deltaContent,
13326
13429
  reasoning: deltaReasoning || void 0,
13430
+ reasoningTokens,
13431
+ reasoningDeltaCount: deltaReasoning ? 1 : void 0,
13327
13432
  createdAt: /* @__PURE__ */ new Date(),
13328
13433
  status: "streaming",
13329
13434
  turnId,
@@ -13399,6 +13504,11 @@ function applyLlmResponse(state, event) {
13399
13504
  }
13400
13505
  const inputTokens = numberValue(event.data.input_tokens);
13401
13506
  const outputTokens = numberValue(event.data.output_tokens);
13507
+ const reasoningTokens = numberValue(event.data.reasoning_tokens);
13508
+ if (reasoningTokens > 0) {
13509
+ const responseIdx = findLastAssistantIndex(messages);
13510
+ messages[responseIdx] = { ...messages[responseIdx], reasoningTokens };
13511
+ }
13402
13512
  return {
13403
13513
  ...state,
13404
13514
  messages,
@@ -13407,7 +13517,7 @@ function applyLlmResponse(state, event) {
13407
13517
  tokenUsage: {
13408
13518
  inputTokens,
13409
13519
  outputTokens,
13410
- reasoningTokens: numberValue(event.data.reasoning_tokens),
13520
+ reasoningTokens,
13411
13521
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
13412
13522
  totalTokens: inputTokens + outputTokens,
13413
13523
  contextWindow: numberValue(event.data.context_window),
@@ -13426,6 +13536,8 @@ ${addition}`;
13426
13536
  }
13427
13537
  function applyLlmThinking(state, event) {
13428
13538
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
13539
+ const reasoningTokens = optionalNumberValue(event.data.reasoning_tokens);
13540
+ const reasoningDeltaCount = optionalNumberValue(event.data.reasoning_delta_count);
13429
13541
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13430
13542
  const messages = [...state.messages];
13431
13543
  const idx = findLastAssistantIndex(messages);
@@ -13434,12 +13546,16 @@ function applyLlmThinking(state, event) {
13434
13546
  prev?.toolCalls?.length && prev.toolCalls.every((tc) => tc.status !== "running")
13435
13547
  );
13436
13548
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
13437
- if (!prev || allToolsDone || hasUserAfter) {
13549
+ 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);
13550
+ const differentIteration = !!prev && (turnId !== void 0 && prev.turnId !== void 0 && turnId !== prev.turnId || iterationIndex !== void 0 && prev.iterationIndex !== void 0 && iterationIndex !== prev.iterationIndex);
13551
+ if (!prev || allToolsDone && !sameIteration || hasUserAfter || differentIteration) {
13438
13552
  messages.push({
13439
13553
  id: `evt-${event.eventId}`,
13440
13554
  role: "assistant",
13441
13555
  content: "",
13442
13556
  reasoning: reasoningText,
13557
+ reasoningTokens: reasoningTokens ?? void 0,
13558
+ reasoningDeltaCount: reasoningDeltaCount ?? void 0,
13443
13559
  createdAt: /* @__PURE__ */ new Date(),
13444
13560
  status: "streaming",
13445
13561
  turnId,
@@ -13449,6 +13565,8 @@ function applyLlmThinking(state, event) {
13449
13565
  messages[idx] = {
13450
13566
  ...prev,
13451
13567
  reasoning: appendText(prev.reasoning, reasoningText),
13568
+ reasoningTokens: reasoningTokens ?? prev.reasoningTokens,
13569
+ reasoningDeltaCount: reasoningDeltaCount ?? prev.reasoningDeltaCount,
13452
13570
  turnId: turnId ?? prev.turnId,
13453
13571
  iterationIndex: iterationIndex ?? prev.iterationIndex
13454
13572
  };