@invergent/agent-chat-react 2.15.8 → 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.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
@@ -10578,6 +10578,17 @@ function useSmoothStream(target, isStreaming) {
10578
10578
  return displayedLen >= target.length ? target : target.slice(0, displayedLen);
10579
10579
  }
10580
10580
 
10581
+ // src/runtime/reasoning-tokens.ts
10582
+ function formatReasoningTokens(tokens) {
10583
+ if (tokens < 1e3) return String(tokens);
10584
+ return `${(tokens / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
10585
+ }
10586
+ function reasoningTokenLabel(message, live) {
10587
+ const tokens = message.reasoningTokens ?? message.reasoningDeltaCount;
10588
+ if (tokens === void 0) return live ? "Thinking..." : "Thought";
10589
+ return `${live ? "Thinking..." : "Thought \xB7"} ${formatReasoningTokens(tokens)} tokens`;
10590
+ }
10591
+
10581
10592
  // src/lib/next-action.ts
10582
10593
  var _NEXT_ACTION_RE = /<next_action([^>]*)>([\s\S]*?)<\/next_action>\s*/gi;
10583
10594
  var _PARTIAL_NEXT_ACTION_FOOTER_RE = /(?:^|\n)\s*<(?:n(?:e(?:x(?:t(?:_(?:a(?:c(?:t(?:i(?:o(?:n[\s\S]*)?)?)?)?)?)?)?)?)?)?)?$/i;
@@ -12133,7 +12144,7 @@ function SimpleFinalAnswer({
12133
12144
  function deriveIterationLabel(message) {
12134
12145
  const calls = visibleToolCalls(message);
12135
12146
  if (calls.length === 0) {
12136
- return message.reasoning ? "Thought through the problem" : null;
12147
+ return message.reasoning || message.reasoningTokens ? reasoningTokenLabel(message, false) : null;
12137
12148
  }
12138
12149
  if (calls.every((tc) => tc.toolName === "skill_view")) {
12139
12150
  return skillViewLabel(calls);
@@ -12165,11 +12176,28 @@ function isIterationLive(message) {
12165
12176
  }
12166
12177
  return message.status === "streaming";
12167
12178
  }
12179
+ function hasInlineRunningIndicator(messages, viewMode) {
12180
+ for (let i = messages.length - 1; i >= 0; i--) {
12181
+ const message = messages[i];
12182
+ if (message.role === "user") return false;
12183
+ if (message.role !== "assistant") continue;
12184
+ if (viewMode === "simple") {
12185
+ if (message.toolCalls?.some((tc) => tc.toolName === "ask_user_question")) {
12186
+ return false;
12187
+ }
12188
+ return isIterationLive(message) && (!!message.toolCalls?.length || !message.content || message.content === message.reasoning);
12189
+ }
12190
+ return messageToEntries(message, i === messages.length - 1, {}).some(
12191
+ (entry) => entry.kind === "reasoning" && entry.isStreaming
12192
+ );
12193
+ }
12194
+ return false;
12195
+ }
12168
12196
  function liveIterationLabel(message) {
12169
12197
  const running = (message.toolCalls ?? []).filter(
12170
12198
  (tc) => tc.status === "running" && !isHiddenSimpleTool(tc)
12171
12199
  );
12172
- if (running.length === 0) return "Thinking\u2026";
12200
+ if (running.length === 0) return reasoningTokenLabel(message, true);
12173
12201
  if (running.every((tc) => tc.toolName === "skill_view")) {
12174
12202
  return `${skillViewLabel(running)}\u2026`;
12175
12203
  }
@@ -12226,44 +12254,16 @@ function SimpleDetailRow({ icon: Icon, children }) {
12226
12254
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
12227
12255
  ] });
12228
12256
  }
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
12257
  function IterationExpanded({
12258
12258
  message
12259
12259
  }) {
12260
12260
  const calls = visibleToolCalls(message);
12261
- const reasoning = (message.reasoning ?? "").trim();
12261
+ const hasReasoning = !!message.reasoning || !!message.reasoningTokens;
12262
12262
  const anyVisibleRunning = calls.some((tc) => tc.status === "running");
12263
- const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
12264
- if (!reasoning && calls.length === 0) return null;
12263
+ const showDone = !anyVisibleRunning && (calls.length > 0 || hasReasoning);
12264
+ if (!hasReasoning && calls.length === 0) return null;
12265
12265
  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 }) }),
12266
+ hasReasoning && /* @__PURE__ */ jsx(SimpleDetailRow, { icon: ClockIcon, children: /* @__PURE__ */ jsx("span", { children: reasoningTokenLabel(message, false) }) }),
12267
12267
  calls.map((tc) => {
12268
12268
  if (tc.toolName === "todo") {
12269
12269
  return /* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(TodoToolBlock, { tc }) }, tc.id);
@@ -12278,6 +12278,7 @@ function IterationExpanded({
12278
12278
  }
12279
12279
  function IterationGroup({
12280
12280
  message,
12281
+ isActive = true,
12281
12282
  sessionId,
12282
12283
  artifactFallbacks,
12283
12284
  onFileSelect
@@ -12297,7 +12298,7 @@ function IterationGroup({
12297
12298
  }
12298
12299
  ) });
12299
12300
  }
12300
- if (isIterationLive(message)) {
12301
+ if (isActive && isIterationLive(message)) {
12301
12302
  return /* @__PURE__ */ jsx(
12302
12303
  OrbShimmerLabel,
12303
12304
  {
@@ -12311,6 +12312,9 @@ function IterationGroup({
12311
12312
  if (!label) {
12312
12313
  return null;
12313
12314
  }
12315
+ if (!summary && visibleToolCalls(message).length === 0) {
12316
+ return /* @__PURE__ */ jsx("p", { className: "py-0.5 text-sm text-muted-foreground", children: label });
12317
+ }
12314
12318
  const labelTone = summary ? "text-muted-foreground italic" : "text-muted-foreground";
12315
12319
  return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
12316
12320
  /* @__PURE__ */ jsxs(
@@ -12404,6 +12408,7 @@ function LoopResultAffordance({ message }) {
12404
12408
  }
12405
12409
  function SimpleAssistantGroup({
12406
12410
  messages,
12411
+ activeAssistantId,
12407
12412
  isRunning,
12408
12413
  sessionId,
12409
12414
  artifactFallbacks,
@@ -12415,7 +12420,7 @@ function SimpleAssistantGroup({
12415
12420
  const tail = assistantMessages[assistantMessages.length - 1];
12416
12421
  const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
12417
12422
  const tailIsTextOnly = !!tail && !tailHasTools;
12418
- const finalText = tailIsTextOnly && tail.content ? tail.content : "";
12423
+ const finalText = tailIsTextOnly && tail.content !== tail.reasoning ? tail.content : "";
12419
12424
  const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
12420
12425
  const turnComplete = !isRunning && tail?.status === "complete";
12421
12426
  const summaryPref = finalText !== null ? stripAndParseNextAction(finalText).action?.summary ?? "hide" : "hide";
@@ -12452,7 +12457,7 @@ function SimpleAssistantGroup({
12452
12457
  );
12453
12458
  }
12454
12459
  if (message === tail && tailIsTextOnly && finalText) return null;
12455
- const rawContent = (message.content ?? "").trim();
12460
+ const rawContent = message.content === message.reasoning ? "" : (message.content ?? "").trim();
12456
12461
  const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
12457
12462
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
12458
12463
  const askBody = hasUserFacingAskContent(message) ? cleaned : null;
@@ -12462,6 +12467,7 @@ function SimpleAssistantGroup({
12462
12467
  IterationGroup,
12463
12468
  {
12464
12469
  message,
12470
+ isActive: message.id === activeAssistantId,
12465
12471
  sessionId,
12466
12472
  artifactFallbacks,
12467
12473
  onFileSelect
@@ -12494,6 +12500,7 @@ function SimpleAssistantGroup({
12494
12500
  }
12495
12501
  function AssistantGroup({
12496
12502
  messages,
12503
+ activeAssistantId,
12497
12504
  lastGlobalIndex,
12498
12505
  totalMessages,
12499
12506
  isRunning,
@@ -12509,6 +12516,7 @@ function AssistantGroup({
12509
12516
  SimpleAssistantGroup,
12510
12517
  {
12511
12518
  messages,
12519
+ activeAssistantId,
12512
12520
  isRunning,
12513
12521
  sessionId,
12514
12522
  artifactFallbacks,
@@ -12640,6 +12648,15 @@ function ChatThread({
12640
12648
  onOpenIntegrations
12641
12649
  }) {
12642
12650
  const groups = useMemo(() => groupMessages(messages), [messages]);
12651
+ const activeAssistantId = useMemo(() => {
12652
+ if (!isRunning) return void 0;
12653
+ for (let i = messages.length - 1; i >= 0; i--) {
12654
+ const message = messages[i];
12655
+ if (message.role === "user") return void 0;
12656
+ if (message.role === "assistant") return message.id;
12657
+ }
12658
+ return void 0;
12659
+ }, [messages, isRunning]);
12643
12660
  const pendingAsk = useMemo(() => pendingAskCall(messages), [messages]);
12644
12661
  const askPolicy = useMemo(
12645
12662
  () => askComposerPolicy(pendingAsk),
@@ -12647,13 +12664,26 @@ function ChatThread({
12647
12664
  );
12648
12665
  const awaitingInput = askPolicy !== null;
12649
12666
  const orbActivity = useMemo(
12650
- () => deriveOrbActivity(
12651
- messages,
12652
- viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12653
- ),
12667
+ () => {
12668
+ const activity = deriveOrbActivity(
12669
+ messages,
12670
+ viewMode === "simple" ? SIMPLE_MODE_ORB_OPTIONS : void 0
12671
+ );
12672
+ if (viewMode === "simple" && activity.state === "solving") {
12673
+ const latest = [...messages].reverse().find((message) => message.role === "assistant");
12674
+ if (latest && !latest.toolCalls?.some((tc) => tc.status === "running")) {
12675
+ return { ...activity, label: reasoningTokenLabel(latest, true) };
12676
+ }
12677
+ }
12678
+ return activity;
12679
+ },
12654
12680
  [messages, viewMode]
12655
12681
  );
12656
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
12682
+ const inlineRunningIndicator = useMemo(
12683
+ () => hasInlineRunningIndicator(messages, viewMode),
12684
+ [messages, viewMode]
12685
+ );
12686
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput && !inlineRunningIndicator;
12657
12687
  const composerLocked = askPolicy?.locked ?? false;
12658
12688
  const composerDisabled = disabled || composerLocked;
12659
12689
  const composerDisabledReason = composerLocked ? askPolicy?.reason : disabledReason;
@@ -12839,6 +12869,7 @@ function ChatThread({
12839
12869
  AssistantGroup,
12840
12870
  {
12841
12871
  messages: group.messages,
12872
+ activeAssistantId,
12842
12873
  lastGlobalIndex: group.lastGlobalIndex,
12843
12874
  totalMessages: messages.length,
12844
12875
  isRunning,
@@ -13081,8 +13112,17 @@ function applyAgentChatEvent(state, event) {
13081
13112
  return collectResearchSource(withResult, toolName, event.data);
13082
13113
  }
13083
13114
  case "harness.wake":
13084
- case "llm.request":
13085
13115
  return { ...nextState, terminal: false, isRunning: true };
13116
+ case "llm.request":
13117
+ return {
13118
+ ...nextState,
13119
+ terminal: false,
13120
+ isRunning: true,
13121
+ hadDeltas: false,
13122
+ messages: nextState.messages.map(
13123
+ (message) => message.role === "assistant" && message.status === "streaming" && !message.toolCalls?.some((tc) => tc.status === "running") ? { ...message, status: "complete" } : message
13124
+ )
13125
+ };
13086
13126
  case "harness.crash":
13087
13127
  case "stream.timeout":
13088
13128
  return nextState;
@@ -13299,22 +13339,31 @@ function browserMarker(event) {
13299
13339
  function applyLlmDelta(state, event) {
13300
13340
  const deltaContent = stringValue2(event.data.content);
13301
13341
  const deltaReasoning = stringValue2(event.data.reasoning);
13342
+ 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
13343
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13303
13344
  const messages = [...state.messages];
13304
13345
  const lastIdx = findLastAssistantIndex(messages);
13305
13346
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
13306
13347
  const hasUserAfter = lastIdx >= 0 && hasUserAfterIndex(messages, lastIdx);
13348
+ const matchesIteration = (turnId === void 0 || lastMsg?.turnId === turnId) && (iterationIndex === void 0 || lastMsg?.iterationIndex === iterationIndex);
13349
+ const sameIteration = turnId !== void 0 && iterationIndex !== void 0 && matchesIteration;
13350
+ if (reasoningTokens !== void 0 && !deltaContent && !deltaReasoning && lastMsg && !hasUserAfter && matchesIteration) {
13351
+ messages[lastIdx] = { ...lastMsg, reasoningTokens };
13352
+ return { ...state, messages };
13353
+ }
13307
13354
  const allToolsDone = Boolean(
13308
13355
  lastMsg?.toolCalls?.length && lastMsg.toolCalls.every((tc) => tc.status !== "running")
13309
13356
  );
13310
13357
  const canAppend = Boolean(
13311
- lastMsg && lastMsg.status === "streaming" && !allToolsDone && !hasUserAfter
13358
+ lastMsg && lastMsg.status === "streaming" && (!allToolsDone || sameIteration) && !hasUserAfter && matchesIteration
13312
13359
  );
13313
13360
  if (canAppend && lastMsg) {
13314
13361
  messages[lastIdx] = {
13315
13362
  ...lastMsg,
13316
13363
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
13317
13364
  reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
13365
+ reasoningTokens: reasoningTokens ?? lastMsg.reasoningTokens,
13366
+ reasoningDeltaCount: deltaReasoning ? (lastMsg.reasoningDeltaCount ?? 0) + 1 : lastMsg.reasoningDeltaCount,
13318
13367
  turnId: turnId ?? lastMsg.turnId,
13319
13368
  iterationIndex: iterationIndex ?? lastMsg.iterationIndex
13320
13369
  };
@@ -13324,6 +13373,8 @@ function applyLlmDelta(state, event) {
13324
13373
  role: "assistant",
13325
13374
  content: deltaContent,
13326
13375
  reasoning: deltaReasoning || void 0,
13376
+ reasoningTokens,
13377
+ reasoningDeltaCount: deltaReasoning ? 1 : void 0,
13327
13378
  createdAt: /* @__PURE__ */ new Date(),
13328
13379
  status: "streaming",
13329
13380
  turnId,
@@ -13399,6 +13450,11 @@ function applyLlmResponse(state, event) {
13399
13450
  }
13400
13451
  const inputTokens = numberValue(event.data.input_tokens);
13401
13452
  const outputTokens = numberValue(event.data.output_tokens);
13453
+ const reasoningTokens = numberValue(event.data.reasoning_tokens);
13454
+ if (reasoningTokens > 0) {
13455
+ const responseIdx = findLastAssistantIndex(messages);
13456
+ messages[responseIdx] = { ...messages[responseIdx], reasoningTokens };
13457
+ }
13402
13458
  return {
13403
13459
  ...state,
13404
13460
  messages,
@@ -13407,7 +13463,7 @@ function applyLlmResponse(state, event) {
13407
13463
  tokenUsage: {
13408
13464
  inputTokens,
13409
13465
  outputTokens,
13410
- reasoningTokens: numberValue(event.data.reasoning_tokens),
13466
+ reasoningTokens,
13411
13467
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
13412
13468
  totalTokens: inputTokens + outputTokens,
13413
13469
  contextWindow: numberValue(event.data.context_window),
@@ -13426,6 +13482,8 @@ ${addition}`;
13426
13482
  }
13427
13483
  function applyLlmThinking(state, event) {
13428
13484
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
13485
+ const reasoningTokens = optionalNumberValue(event.data.reasoning_tokens);
13486
+ const reasoningDeltaCount = optionalNumberValue(event.data.reasoning_delta_count);
13429
13487
  const { turnId, iterationIndex } = readTurnMeta(event.data);
13430
13488
  const messages = [...state.messages];
13431
13489
  const idx = findLastAssistantIndex(messages);
@@ -13434,12 +13492,16 @@ function applyLlmThinking(state, event) {
13434
13492
  prev?.toolCalls?.length && prev.toolCalls.every((tc) => tc.status !== "running")
13435
13493
  );
13436
13494
  const hasUserAfter = idx >= 0 && hasUserAfterIndex(messages, idx);
13437
- if (!prev || allToolsDone || hasUserAfter) {
13495
+ 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);
13496
+ const differentIteration = !!prev && (turnId !== void 0 && prev.turnId !== void 0 && turnId !== prev.turnId || iterationIndex !== void 0 && prev.iterationIndex !== void 0 && iterationIndex !== prev.iterationIndex);
13497
+ if (!prev || allToolsDone && !sameIteration || hasUserAfter || differentIteration) {
13438
13498
  messages.push({
13439
13499
  id: `evt-${event.eventId}`,
13440
13500
  role: "assistant",
13441
13501
  content: "",
13442
13502
  reasoning: reasoningText,
13503
+ reasoningTokens: reasoningTokens ?? void 0,
13504
+ reasoningDeltaCount: reasoningDeltaCount ?? void 0,
13443
13505
  createdAt: /* @__PURE__ */ new Date(),
13444
13506
  status: "streaming",
13445
13507
  turnId,
@@ -13449,6 +13511,8 @@ function applyLlmThinking(state, event) {
13449
13511
  messages[idx] = {
13450
13512
  ...prev,
13451
13513
  reasoning: appendText(prev.reasoning, reasoningText),
13514
+ reasoningTokens: reasoningTokens ?? prev.reasoningTokens,
13515
+ reasoningDeltaCount: reasoningDeltaCount ?? prev.reasoningDeltaCount,
13452
13516
  turnId: turnId ?? prev.turnId,
13453
13517
  iterationIndex: iterationIndex ?? prev.iterationIndex
13454
13518
  };
@@ -16347,9 +16411,10 @@ async function latexToSvg(latex, display = true) {
16347
16411
  }
16348
16412
  var FormulaCache = class {
16349
16413
  constructor(onReady) {
16350
- __publicField(this, "onReady", onReady);
16351
16414
  __publicField(this, "entries", /* @__PURE__ */ new Map());
16352
16415
  __publicField(this, "pending", /* @__PURE__ */ new Set());
16416
+ __publicField(this, "onReady");
16417
+ this.onReady = onReady;
16353
16418
  }
16354
16419
  get(latex, fontSize) {
16355
16420
  const hit = this.entries.get(latex);
@@ -16462,11 +16527,13 @@ function strokePointsFromEvent(event) {
16462
16527
  }
16463
16528
  var StrokeBuilder = class {
16464
16529
  constructor(color, width) {
16465
- __publicField(this, "color", color);
16466
- __publicField(this, "width", width);
16467
16530
  /** Flat [x0,y0,x1,y1,...], matching the ink object's `pts`. */
16468
16531
  __publicField(this, "pts", []);
16469
16532
  __publicField(this, "started", false);
16533
+ __publicField(this, "color");
16534
+ __publicField(this, "width");
16535
+ this.color = color;
16536
+ this.width = width;
16470
16537
  }
16471
16538
  begin(pt) {
16472
16539
  this.started = true;