LibreChat/packages/data-provider/src
Danny Avila fb8ae881cf
⏱️ feat: Show Run-Step Durations On Tool Cards (#14892)
* ⏱️ feat: Show Run-Step Durations On Tool Cards

Surfaces how long each tool call took, derived from the `closed_at` /
`created_at` pair already carried by `on_run_step_closed` — the same event
#14871 and #14873 use for the terminal status. No new event, no new SDK
surface.

The duration is stamped onto the content part at the same three sites as
`runStepStatus`, so it survives a reload and a resumable reconnect rather
than living only on the live React message:

- `callbacks.js`, on the aggregated part before the event is forwarded
- `RedisJobStore`, in the host-authored replay reconstruction branch
- `useStepHandler`, on the live message

Rendering lands in the shared `ProgressText`, which nine tool cards already
use, rather than in each card: one place decides whether a duration is shown
and how it reads, and the cards only forward the number. That keeps this from
adding a tenth independent state derivation to a component family whose
label/announcement/progress split is already the subject of AI-1810.

The value is deliberately absent rather than zero whenever it would be a
guess — no `created_at`, non-finite input, or a negative elapsed time from
two clocks that disagree, which is now reachable because a step can be opened
in one process and closed in another after a checkpoint resume. Sub-second
durations are suppressed as noise, and it renders only on a settled,
non-error card, where the slot is not already carrying the cancelled icon or
the error suffix.

For assistive technology the compact form (`3.5s`) is hidden and paired with
a spoken equivalent ("took 3.5 seconds"), both inside the button, so the
accessible name carries the duration without an `aria-live` region
re-announcing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🎨 style: Sort Imports In Touched Files

The import-sort gate runs against the files a PR changes, so pre-existing
drift in `ProgressText.tsx` and `RedisJobStore.ts` surfaced on this branch.
Both were already unsorted on `dev`; this is the sorter's output, with no
semantic change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🐛 fix: Accept Partial Timestamps In Run-Step Duration Helper

`getReportableRunStepDurationMs` declared its parameter as
`Pick<RunStepClosedEvent, 'created_at' | 'closed_at'>`, where `closed_at` is
required. That contradicted the function's own purpose: every guard inside it
exists precisely to handle stamps that may be missing.

The Redis replay branch reconstructs closures from persisted JSON and holds
nothing stronger than "might be a number", so it failed to typecheck against
the narrower signature.

Widened to an exported `RunStepTimestamps` shape with both stamps optional,
rather than asserting at the call site — an assertion would move the decision
about what is trustworthy somewhere it cannot be enforced, which is the thing
the helper exists to centralize. Callers holding a fully-typed event still
pass, since a required field satisfies an optional one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🐛 fix: Suppress Duration When Failure Arrives As errorSuffix Alone

At every call site `error` carries cancellation while failure travels
through `errorSuffix` with `error` false, so gating the duration on
`!error` alone rendered "· 3.5s" beside "· failed" — and announced it.
The gate now checks both terminal-failure channels.

The original test pinned only the `error: true` path, which is why this
survived; the failed-via-suffix path is now pinned separately, both the
visible and the announced half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🧩 refactor: Persist Raw Run-Step Durations, Threshold At Render Only

The three stamp sites filtered through the 1-second reportability
threshold before persisting, baking a presentation rule into stored
data: a 900ms step stored nothing, making "fast" indistinguishable from
"not derivable" and unrecoverable if the display rule ever changes.

Stamp sites now persist the raw `getRunStepDurationMs` value — absent
only when genuinely not derivable — and the renderer alone decides what
is worth showing, which `ProgressText` already did. Rendering is
unchanged. `getReportableRunStepDurationMs` is removed; it existed only
to serve the write-time filter, and a test now pins that sub-threshold
durations survive to storage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🐛 fix: Suppress Duration On Backgrounded Bash And Code Cards

A backgrounded call's run step closes when dispatch returns the handle,
so the stamped duration is the dispatch time. Rendering it beside
"Running/Finished in background" misstated a detached task's runtime as
seconds — and violated the "settled card only" rule, since the card is
still tracking the detached run. Scope is exactly the two cards that
parse background handles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🌍 fix: Format The Sub-10s Decimal For The Active Locale

The fractional seconds value was interpolated as a raw JS number, which
hardcodes the en-US decimal point into every language — "1.4s" where
the locale writes "1,4 s" — and translators cannot fix a number
formatted in code. The value is now formatted via Intl.NumberFormat
with i18n.language, following MessageTimestamp's pattern of threading
the language into the util; plural-key selection stays on the numeric
value. A malformed language tag falls back to the plain number.

Also documents the two accepted limits of the derivation, so they read
as decisions rather than oversights: positive clock skew is
undetectable from a single stamp pair, and the value is wall-clock
elapsed, so a step held open across a suspension (checkpoint resume,
HITL approval wait) includes that time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🐛 fix: Persist A Durable `backgrounded` Marker Through Harvest; Localize Minute Digits

Codex round 3, both findings confirmed.

**Background origin survived only as transient state.** The dispatch
handle in `tool_call.output` and the live status-marker attachment are
both gone once the harvester patches the settled task's stdout over the
handle — so the round-2 suppression (`backgroundHandle == null`) came
back on after harvest or reload, showing dispatch time as the task's
runtime. Following the same rule as e4bd15d (persist facts, decide at
render): the harvest patch now stamps `backgrounded: true` onto the
tool call in the same atomic write that erases the handle — on the heal
path too, which re-applies over full-row saves that reverted the part.
The cards gate on handle-or-marker; the dispatch duration itself stays
stored.

**Minute-branch digits bypassed locale formatting.** The seconds branch
went through Intl.NumberFormat while minutes interpolated raw numbers,
so Arabic/Persian locales flipped to ASCII digits above one minute. All
interpolated values now flow through the (renamed) formatDurationValue;
an ar-EG test pins the localized digits.

data-schemas cannot be installed in this environment (same npm ci 403 as
packages/api), so message.ts/harvest.ts are syntax-checked with
resolution off and otherwise verified by review; CI runs their real
typecheck and suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🧪 test: Assert The `markBackgrounded` Stamp In Harvest Expectations

The successful-harvest test's exact `toHaveBeenCalledWith` object did
not include the newly forwarded `markBackgrounded`, so the API suite
would fail on it. All three harvest-call expectations now assert
`markBackgrounded: true` — the exact-object one of necessity, the two
`objectContaining` ones deliberately, since the durable stamp (on the
best-effort file-failure path and the reapply heal alike) is now part
of the behavior under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

* 🎨 style: Wrap Harvest Spec Expectation Per Prettier

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-16 16:17:02 -04:00
..
react-query 📌 fix: Fetch Pinned Chats Independently of the Chats List (#14860) 2026-08-16 11:28:33 -04:00
types ⏱️ feat: Show Run-Step Durations On Tool Cards (#14892) 2026-08-16 16:17:02 -04:00
accessPermissions.ts
actions.ts fix: Bind Action Servers to Metadata Ports (#14575) 2026-08-02 13:41:31 -04:00
api-endpoints.ts 🪢 feat: add Langfuse session links (#14776) 2026-08-12 22:25:23 -04:00
artifacts.ts
azure.ts
balance.spec.ts
balance.ts
bedrock.ts ✳️ feat: Claude Opus 5 Support (#14422) 2026-07-24 21:45:32 -04:00
cloudfront-config.spec.ts
codeEnvRef.spec.ts 🛤️ feat: Per-Agent Code Execution Routing With Stateful Session Scopes (#14848) 2026-08-16 09:42:15 -04:00
codeEnvRef.ts 🛤️ feat: Per-Agent Code Execution Routing With Stateful Session Scopes (#14848) 2026-08-16 09:42:15 -04:00
config.spec.ts 🎙️ fix: Align Speech Engine Configuration With Runtime (#14736) 2026-08-12 13:22:28 -04:00
config.ts 📌 fix: Fetch Pinned Chats Independently of the Chats List (#14860) 2026-08-16 11:28:33 -04:00
createPayload.ts feat: Reliable Interrupt & Steer Escalation and Recovery (#14558) 2026-07-31 20:07:56 -04:00
data-service.ts 🪢 feat: add Langfuse session links (#14776) 2026-08-12 22:25:23 -04:00
feedback.spec.ts 🛡️ fix: validate message feedback payloads (#14500) 2026-07-28 22:17:30 -04:00
feedback.ts 🛡️ fix: validate message feedback payloads (#14500) 2026-07-28 22:17:30 -04:00
file-config.spec.ts 📎 fix: Alias Shell Script MIME Variants to application/x-sh (#14817) 2026-08-14 01:12:23 -04:00
file-config.ts 📎 fix: Alias Shell Script MIME Variants to application/x-sh (#14817) 2026-08-14 01:12:23 -04:00
generate.ts
headers-helpers.ts
index.ts ⏱️ feat: Show Run-Step Durations On Tool Cards (#14892) 2026-08-16 16:17:02 -04:00
keys.ts 📌 fix: Fetch Pinned Chats Independently of the Chats List (#14860) 2026-08-16 11:28:33 -04:00
limits.ts 🗂️ feat: Rework the Projects Dashboard, Sidebar and Scoped Composer (#14866) 2026-08-16 11:06:18 -04:00
mcp.ts 🔌 feat: Add Agent Plugins (Experimental) (#14704) 2026-08-09 08:10:22 -04:00
messages.spec.ts 🪺 fix: Keep Preempt-Abandoned Siblings Nested, Make Message Tree Order-Robust (#14587) 2026-08-02 07:04:52 -04:00
messages.ts 🪺 fix: Keep Preempt-Abandoned Siblings Nested, Make Message Tree Order-Robust (#14587) 2026-08-02 07:04:52 -04:00
models.ts 🎯 refactor: Infer Agents Endpoint for Model Specs Naming an Agent (#14889) 2026-08-16 11:04:52 -04:00
parameterSettings.spec.ts
parameterSettings.ts 🧠 feat: Add GPT-5.6 reasoning.mode + reasoning.context (Responses API) (#14233) 2026-07-13 09:52:42 -04:00
parsers.ts 🧭 feat: Mid-Run Steering and Queued Messages for Agent Runs (#14220) 2026-07-14 10:11:10 -04:00
permissions.ts
request.ts 🛟 fix: Keep File Uploads Alive With SSE Heartbeats (#14295) 2026-07-21 19:27:09 -04:00
roles.spec.ts
roles.ts
runSteps.spec.ts ⏱️ feat: Show Run-Step Durations On Tool Cards (#14892) 2026-08-16 16:17:02 -04:00
runSteps.ts ⏱️ feat: Show Run-Step Durations On Tool Cards (#14892) 2026-08-16 16:17:02 -04:00
schemas.spec.ts 🧠 feat: Add GPT-5.6 reasoning.mode + reasoning.context (Responses API) (#14233) 2026-07-13 09:52:42 -04:00
schemas.ts 🎯 refactor: Infer Agents Endpoint for Model Specs Naming an Agent (#14889) 2026-08-16 11:04:52 -04:00
splitMCPToolKey.spec.ts 🔗 fix: Normalize MCP Tool Keys at Every Producer, Resolve Raw Names via Aliases (#14553) 2026-08-01 07:39:24 -04:00
types.ts 🔖 feat: Bound Parent Activity Phases With an Exclusive End Index (#14768) 2026-08-12 23:43:35 -04:00
upload.spec.ts 🛟 fix: Keep File Uploads Alive With SSE Heartbeats (#14295) 2026-07-21 19:27:09 -04:00
upload.ts 🛟 fix: Keep File Uploads Alive With SSE Heartbeats (#14295) 2026-07-21 19:27:09 -04:00
utils.ts