* 📤 feat: Model-Aware Max Output Tokens for Google/Gemini
Resolves#13384.
Current Gemini text models (2.5 and 3+, including Gemini 3.5 Flash)
support 64K output tokens, but LibreChat defaulted every Google model
to the legacy 8K value — most visibly in the Agents model-parameter
panel.
- Add model-aware `reset`/`set` to `googleSettings.maxOutputTokens`,
mirroring the Anthropic pattern: Gemini 2.5/3+ -> 65536, legacy
(2.0 and earlier) and Gemma -> 8192.
- Resolve the default server-side in `getGoogleConfig` and in the
Agents, preset, and standard Google settings panels via a shared
`applyModelAwareDefaults` helper.
- Make `compactGoogleSchema` and `generateGoogleSchema` model-aware so
explicit user values are preserved and not overwritten.
* 🛡️ fix: Cap Google max output at Vertex-safe limits
Addresses Codex review (P1) on #13390. Vertex AI caps current Gemini
text models at 65,535 output tokens (vs 65,536 on AI Studio) and image
models at 32,768, so an unconditional 65,536 default could make
otherwise-default Vertex requests fail validation.
- Lower the modern text default/ceiling to 65535 (valid on both Vertex
and AI Studio).
- Resolve Gemini image models (e.g. gemini-2.5-flash-image) to 32768.
- Add reset/set + getGoogleConfig tests for image models and the Vertex
default path.
* 🧮 fix: Respect configured Google defaults and legacy image caps
Addresses Codex review round 2 on #13390 (one P2, two P3).
- P2 (llm.ts): apply the model-aware maxOutputTokens default as the final
fallback instead of pre-filling it, so an explicit value, `defaultParams`,
and `addParams` all take precedence and `dropParams` is honored. Empty-string
values stay stripped (preserves prior Gemini empty-payload handling).
- P3 (panels): pass the resolved params endpoint (`overriddenEndpointKey`) to
`applyModelAwareDefaults`, so custom endpoints with
`defaultParamsEndpoint: 'google'` also surface the model-aware default.
- P3 (schemas): nest the image-model check inside the 2.5+/3+ version check, so
legacy image IDs (e.g. gemini-2.0-flash-preview-image-generation) keep the 8K
cap instead of being treated as 32K models.
- Add tests for defaultParams precedence, dropParams, legacy image models, and
the Vertex default path.
* 🧭 fix: Base Google defaults on final model and configured overrides
Addresses Codex review round 3 on #13390 (two P2).
- llm.ts: resolve the model-aware maxOutputTokens default from the final
`llmConfig.model` (after defaultParams/addParams) instead of the model
captured from modelOptions, so a model forced via addParams/paramDefinitions
on a Google-compatible custom endpoint gets its correct limit.
- Panels: apply model-aware defaults to the built-in settings first, then
overlay `customParams.paramDefinitions`, so an admin-configured
maxOutputTokens default wins in the UI (consistent with backend precedence).
- Add parameterSettings.spec for applyModelAwareDefaults (incl. override
precedence) and a getGoogleConfig final-model test.
* 🛡️ fix: Cap Default Limit on Agent List Queries (#13363)
`GET /api/agents` accepted unbounded requests: when the client omitted
`limit`, the value flowed straight into `getListAgentsByAccess`, which
set `isPaginated = false` and issued an uncapped MongoDB query. Combined
with the unindexed `findPubliclyAccessibleResources` AclEntry scan run
on every request, this produced 10-19s response times and stalled the
connection pool on instances with 100+ agents.
- Default `limit` to 100 in the route handler so client requests without
`?limit=` paginate by default.
- Default `limit` to 100 in `getListAgentsByAccess` itself as
defense-in-depth. The function already caps numeric limits at 100, so
there is no client-facing change.
- Pass `limit: null` explicitly in the actions route, which legitimately
needs the full editable-agent set, to preserve its existing behavior.
- Add regression tests covering the default cap and the explicit
unbounded opt-out.
* 🛡️ fix: Avoid agent-list regression for users with 100+ agents
Codex review pointed out that capping `getListAgentsByAccess` at 100
silently truncated agents past the first page for the four consumers
(`useAgentsMap`, `AgentSelect`, `ModelSelectorContext`, `useMentions`)
that read `res.data` without following `has_more`/`after`.
- Raise the function's hard cap from 100 to 1000 to match
`MAX_AVATAR_REFRESH_AGENTS`, the realistic upper bound the
avatar-refresh path already assumes. (Side effect: the avatar refresh
call site was silently being capped at 100 by the old normalize step.)
- In `useListAgentsQuery`, merge `limit: 1000` into params so the four
consumers above get the user's full accessible set in a single
round-trip instead of needing cursor pagination.
- Route handler default stays at 100 as defense-in-depth for any other
caller that omits `limit`.
- Add a regression test asserting an explicit `limit` above 100 now
returns the full set instead of being clipped.
* 🪢 fix: Keep agent-list cache key stable for mutations
Codex P2 review noted that folding `limit: 1000` into the cache key
broke `allAgentViewAndEditQueryKeys` in `Agents/mutations.ts`, which
references `[QueryKeys.agents, { requiredPermission }]` directly across
eight mutation handlers. After my prior change the cached entry lived
under `[QueryKeys.agents, { limit: 1000, requiredPermission }]`, so
create/update/delete/avatar/action mutations stopped updating the list
the four consumer hooks render — and with `refetchOnMount` and focus/
reconnect refetches disabled, the UI would stay stale until something
else triggered a fetch.
Split the merged limit out of the cache key: the request to
`dataService.listAgents` still uses `requestParams` (with the default
limit applied), but the React Query cache key uses the caller's `params`
as-is. The mutation cache updates land again, and the request still
returns the user's full accessible set in one round-trip.
* 🛡️ fix: Index AclEntry and paginate agent list internally (#13363)
Completes the perf fix for #13363 properly — resolves both the
unbounded ACL scans Copilot flagged and Codex's tension between "show
all agents" and "don't bypass the server cap".
Backend:
- Add a compound index on `{ principalType, resourceType, permBits,
resourceId }` to the AclEntry schema. This is the index missing for
`findPublicResourceIds` and the public branch of the `$or` in
`findAccessibleResources`, both of which previously fell back to a
collection scan on every `GET /api/agents`. Adds an `explain`-based
regression test asserting the public query no longer COLLSCANs.
Client:
- Rewrite `useListAgentsQuery` to follow the server's cursor
pagination internally and concatenate every page into a single flat
`AgentListResponse`. Consumers (`useAgentsMap`, `AgentSelect`,
`ModelSelectorContext`, `useMentions`) get the user's complete
accessible-agent set without any of them needing to learn about
cursors, and each individual request uses the server's default
page size (so the route's 100-default defense-in-depth fires for
real). Cache key shape is unchanged, so the eight mutation handlers
in `Agents/mutations.ts` keep matching `allAgentViewAndEditQueryKeys`
and update the cached list as before.
- Drop the `FULL_AGENT_LIST_LIMIT = 1000` injection added in the
previous commit — no longer needed once pagination handles the full
set, and removing it stops bypassing the route default.
* 🧹 fix: CI fallout from C-done-properly refactor
- Collapse multi-line `fetchAllAgentPages` signature in queries.ts so
prettier stops complaining.
- In the new public-principal index test, grant one ACL entry before
calling `.explain()` so the collection exists (otherwise mongo returns
`nonExistentNamespace` and there is no winning plan to inspect).
- Cast the `.explain('queryPlanner')` result to a typed shape — the
mongoose return type doesn't expose `queryPlanner` directly and was
failing the TypeScript check.
* 🧪 fix: Test the AclEntry public-principal index via hint, not planner choice
The previous test asserted the query planner did not pick COLLSCAN for
the public-principal lookup. That assertion fails on small collections
(under the planner's collection-size heuristic) — the index exists and
is usable, but with a single document in the test the planner correctly
chooses COLLSCAN as the cheaper plan.
Reshape the assertion:
1. Confirm the new compound index is actually declared by inspecting
`collection.indexes()` after `syncIndexes()`.
2. Force the planner to that index via `.hint()` and assert the winning
plan is `IXSCAN` — proves the index is real and serves this query
shape, without depending on collection-size heuristics.
* 🧹 chore: Slim down verbose comments
The JSDoc and inline comments added across the perf fix had drifted
into multi-paragraph rationale better suited to the PR description than
the source. Collapse to single-line JSDoc that just describes what each
piece does; drop the inline comment in `actions.js` entirely — the call
is self-evident.
* feat: add Claude Opus 4.8 support
* fix: omit sampling params for Claude Opus 4.8
* fix: flatten Bedrock beta header merge
* fix: strip Bedrock sampling params for Opus 4.8
In SidePanel/MCPBuilder/MCPServerCard the server title was rendered in
a span carrying the Tailwind truncate utility. overflow: hidden does
not apply to inline boxes, so long server names overflowed their slot
and ran underneath the Edit, Reconnect, and Revoke icons.
The title now renders in a div so truncate actually clips with an
ellipsis. No other styling changes.
* feat: Add Bedrock API key support
* fix: Respect Bedrock credential mode
* fix: Support mixed Bedrock credential forms
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* 🏗️ refactor: Derive App Version from Root package.json + Add buildInfo Schema
The hardcoded `Constants.VERSION` in `data-provider` is now replaced at
rollup build time via `@rollup/plugin-replace`, sourcing from the root
`package.json` so version bumps are a single-file change.
Adds the shape needed by the rest of the series:
- `interface.buildInfo` boolean flag (default `true`) — lets self-hosters
opt out of exposing commit/branch/date.
- `buildInfo` on `TStartupConfig` — commit/commitShort/branch/buildDate.
- `SettingsTabValues.ABOUT` — new settings tab enum value.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🛠️ feat: Add Build Metadata Resolver and Expose via /api/config
Adds `resolveBuildInfo()` in `@librechat/api` that surfaces commit SHA,
branch, and build date from (in order) `BUILD_*` env vars, then local git
metadata. Result is cached per-process.
`/api/config` includes a `buildInfo` field on both authenticated and
anonymous responses when `interface.buildInfo !== false` and at least one
resolver field is populated. Omitted entirely otherwise.
Designed so pre-built Docker images carry metadata via build-arg while
source installs pick it up from `.git` — no manual version tracking.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* ℹ️ feat: Add Settings → About Panel with Diagnostics Copy
New Settings tab that renders the running build's version, commit (short
SHA), branch, and build date in a monospaced block alongside a "Copy
diagnostics" button that emits a preformatted text blob for pasting into
support issues.
Tab is hidden when `interface.buildInfo` is set to `false`. Reads from
`startupConfig.buildInfo` provided by `/api/config`.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🐳 ci: Inject BUILD_COMMIT/BRANCH/DATE into Docker Images
Adds optional `BUILD_COMMIT`, `BUILD_BRANCH`, `BUILD_DATE` ARGs to both
`Dockerfile` and `Dockerfile.multi`, wired as `ENV` vars in the runtime
stage so the backend's `resolveBuildInfo` picks them up.
All image-publishing workflows (`tag`, `main`, `dev`, `dev-branch`,
`dev-staging`) now compute `${github.sha}`, `${github.ref_name}`, and a
UTC timestamp, then pass them to `docker/build-push-action` as
`build-args`.
Defaults are empty — non-CI builds (local `docker build`) still work,
and the backend falls back to local `.git` metadata if ARGs aren't set.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 📝 docs: Direct Bug Reporters to Settings → About for Version Info
The previous instructions (`docker images | grep librechat`,
`git rev-parse HEAD`) only worked for a subset of deployments and
rarely produced a commit SHA for users pulling pre-built images.
Point users to the new in-app Settings → About panel's
"Copy diagnostics" button, which captures version, commit, branch,
build date, and user agent in a single preformatted block. Fallback
instructions preserved for older installs.
Ref: https://github.com/danny-avila/LibreChat/issues/12406
* 🐳 fix: Move BUILD_* ENV to End of Docker Stages to Preserve Layer Cache
Per-commit BUILD_COMMIT/BUILD_DATE changes were being promoted to ENV
before `npm ci` / `npm run frontend` (single-stage) and before
`npm ci --omit=dev` (multi-stage api-build), which invalidated the cache
for every subsequent layer on every CI run.
Move the ARG/ENV block below the heavy install and build steps in both
Dockerfiles. Metadata is still available in the runtime image but no
longer busts layer reuse.
Addresses codex review on #12756.
* 🔧 fix: Propagate interface.buildInfo=false to Unauthenticated /api/config
The unauthenticated branch of `/api/config` was emitting an `interface`
object only when `privacyPolicy` or `termsOfService` was set, which
meant an admin's explicit `interface.buildInfo: false` opt-out was never
visible to anonymous/guest clients. `Settings.tsx` gates the About tab
on `startupConfig?.interface?.buildInfo !== false`, so a missing field
fell through as "enabled" for those clients.
Include `interface.buildInfo: false` in the unauth payload whenever it's
explicitly disabled. Keep the implicit default (true) absent to preserve
the minimal-unauth-payload convention.
Addresses codex review on #12756.
* 🔀 ci: Trigger Dev Image Workflows on Root package.json + Dockerfile Changes
The baked `Constants.VERSION` now reads from the root `package.json` via
rollup-plugin-replace, but the `dev-images.yml` and `dev-branch-images.yml`
path filters only matched `api/**`, `client/**`, `packages/**`. A release
commit that only bumps root `package.json` would not trigger a rebuild,
leaving `latest` dev images with stale Footer/About version metadata.
Include `package.json`, `package-lock.json`, and both Dockerfiles in the
path filters so dependency changes (lockfile rebuilds) and image build
tweaks also rebuild dev images.
Addresses codex review on #12756.
* 🧽 fix: Harden About Panel Lifecycle, A11y, and Loading Gate
Review follow-ups on #12756:
- #1 timer leak: stash the copy-state `setTimeout` in a ref and clear it
from a `useEffect` cleanup so unmounting the Settings dialog mid-toast
doesn't fire `setCopied(false)` on an unmounted component.
- #3 flash of About tab: gate `aboutEnabled` on `startupConfig != null`
so the tab stays hidden until `/api/config` returns. For admins who
disabled `interface.buildInfo`, the tab no longer briefly appears and
vanishes on page load.
- #6 aria-live placement: move the live region off the interactive
button onto a dedicated `<span role="status" aria-live="polite">` so
screen readers announce the copied state, not the full button content
on every re-render.
- #2 missing coverage: add `About.spec.tsx` exercising populated/empty
buildInfo rendering, invalid-date handling, diagnostics clipboard
payload, copy-state toggling, unmount cleanup, and the live region.
* ⚡ perf: Eagerly Resolve Build Info at Module Load
Review follow-up #4 on #12756: `resolveBuildInfo()` calls `execFileSync`
with a 2s timeout on source installs without `BUILD_*` env vars. Paying
this cost on the first HTTP request blocks the event loop mid-flight.
Call `resolveBuildInfo()` once at config route module load so the
resolver's cache is warm before any request arrives. Docker images with
the BUILD_* env vars set sidestep the git path entirely, so this only
affects the edge case of source installs.
* 📝 docs: Document rollup Version Placeholder Contract
Review follow-ups #5 / #8 on #12756. The `__LIBRECHAT_VERSION__`
placeholder relies on a substring replacement rule that only works
because the token appears inside a string literal, and the substitution
only runs during `npm run build:data-provider`.
- Expand the `Constants.VERSION` JSDoc to spell out that consumers read
the placeholder through the built dist bundle; source-level test
imports would see the raw placeholder.
- Add a NOTE above the rollup `replace` config warning future
contributors not to repurpose the token as a bare identifier without
switching to a quoted replacement value.
Non-functional; prevents future contributors from stepping on a subtle
constraint.
* 🪪 fix: Only Toast "Copied" When Clipboard Copy Actually Succeeds
Codex R5 on #12756. `copy-to-clipboard` returns a boolean indicating
whether the underlying `execCommand('copy')` / fallback prompt actually
wrote to the clipboard. The previous handler flipped to the "Copied"
state unconditionally, which in hardened browsers or when the
permission prompt is dismissed would mislead users into filing bug
reports without the diagnostics blob attached.
Gate the state/timer/live-region on the boolean return; silently no-op
on failure rather than showing a false positive. Adds a test asserting
the button label stays at "Copy diagnostics" when the clipboard call
fails.
* 🐳 fix: Derive main image metadata from checkout
* 🪪 fix: Keep About enabled until disabled
* ✅ test: Avoid literal Settings mock text
* 🧱 refactor: Rename Build Info Module
There are two ways to add a file to a conversation:
1. Uploading a new file.
2. Using an existing file (from the side panel).
If you decide to remove the file, the behavior differs depending on
how it was added. If you just uploaded a new file, it gets deleted
from the conversation & the system. But if it's an existing file,
then it only gets removed from the conversation (but not deleted).
However, in both cases, it would show a toast saying that the file
was deleted, which is incorrect for the "existing file" case.
Now we check whether the file is `attached` (to the system) before
showing the deletion toast, and skip showing it if we're not actually
deleting the file.
* feat: support data retention for normal chats
Add retentionMode config variable supporting "all" and "temporary" values.
When "all" is set, data retention applies to all chats, not just temporary ones.
Adds isTemporary field to conversations for proper filtering.
Adapted to new TS method files in packages/data-schemas since upstream
moved models out of api/models/.
Based on danny-avila/LibreChat#10532
Co-Authored-By: WhammyLeaf <233105313+WhammyLeaf@users.noreply.github.com>
(cherry picked from commit 30109e90b0)
* feat: extend data retention to files, tool calls, and shared links
Add expiredAt field and TTL indexes to file, toolCall, and share schemas.
Set expiredAt on tool calls, shared links, and file uploads when
retentionMode is "all" or chat is temporary.
(cherry picked from commit 48973752d3)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: lint/test
(cherry picked from commit 310c514e6a)
* fix: address code review feedback for data retention PR
Critical:
- Fix BookmarkMenu crash: restore optional chaining on conversation
- Fix migration hazard: backward-compatible sidebar filter that also
checks expiredAt for documents without isTemporary field
Major:
- Add logging to getRetentionExpiry error path, align with tools.js
- Add tests for retentionMode: ALL in saveConvo and saveMessage
- Fix share route: apply expiredAt for temporary chats too by
querying the conversation's isTemporary flag server-side
- Add assertions for getRetentionExpiry mocks in process tests
Minor:
- Fix ChatRoute isTemporaryChat to be strictly boolean via Boolean()
- Fix stale test description (expired -> temporary)
- Comment out retentionMode default in example yaml
- Simplify verbose if/else to isTemporary === true
- Add compound index on { user: 1, isTemporary: 1 }
- Remove narrating comment from process.spec.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6bad535f90)
* chore: fix typescript
(cherry picked from commit 826527a46b)
* fix: lint
(cherry picked from commit 77817e80ea)
* fix: use mockSanitizeArtifactPath in retention test
The 'getRetentionExpiry is called with the request object' test
referenced an undefined `mockSanitizeFilename` identifier, breaking
both lint (no-undef) and the test suite. Use the existing
`mockSanitizeArtifactPath` mock that the surrounding tests already
use, since `processCodeOutput` calls `sanitizeArtifactPath` (not
`sanitizeFilename`) before invoking `getRetentionExpiry`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 52ea2da66d)
* fix: forward isTemporary from client for retention on file uploads and tool calls
Server-side `getRetentionExpiry` (file uploads) and the tool-call
controller both read `req.body.isTemporary`, but the file upload
multipart form and the tool-call payload did not include that field.
In `retentionMode: temporary` (default), files uploaded and tool
calls created from temporary chats were therefore retained
indefinitely.
Forward the Recoil `isTemporary` flag in both client paths so the
existing server checks can fire correctly. `ToolParams` gains an
optional `isTemporary` field.
Addresses Codex P1 review feedback on PR #29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7e937df05a)
* test: stub store.isTemporary in useFileHandling test mocks
Previous commit added `useRecoilValue(store.isTemporary)` to the
hook. The test file mocks `~/store` with only `ephemeralAgentByConvoId`
and does not stub `useRecoilValue`, so all 7 cases threw
"Invalid argument to useRecoilValue: expected an atom or selector but
got undefined". Add a stub default export with `isTemporary` and a
`useRecoilValue` mock returning `false`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit eb1609537d)
* fix: harden data retention semantics
* fix: provide sweep request context for expired files
* fix: preserve temporary flags in all-retention updates
* fix: honor assistant versions in retention sweeps
* fix: retain non-temporary flags in all mode
* fix: hide expired retained records
* fix: propagate retained conversation expiry
* fix: refresh meili retention cutoff
* fix: prevent overlapping file sweeps
* fix: show legacy retained conversations
* fix: index legacy retained records
* fix: harden retention cleanup edge cases
* fix: count failed file storage sweeps
* fix: preserve legacy temporary retention
* fix: assign retention sweep worker deterministically
* fix: hide expired shared links on reads
* fix: prevent retention refresh after parent expiry
* fix: break code output retention import cycle
* fix: harden retention review findings
* fix: ignore expired share duplicates
* fix: reject expired retained share creation
* fix: harden retention review edge cases
* fix: address retention audit findings
* fix: enforce expired conversation shares in all retention
* fix: scope temporary upload flag to chat files
* fix: address retention review findings
* fix: address codex retention review findings
* fix: tighten missing storage detection
* test: remove unused file process spec bindings
---------
Co-authored-by: WhammyLeaf <233105313+WhammyLeaf@users.noreply.github.com>
Co-authored-by: Aron Gates <aron@muonspace.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(attachments): add variant prop to AttachmentGroup
* feat(tool-call): add hideImageAttachments prop to ToolCall
* fix(tool-call): keep MCP image outputs visible when tool group auto-collapses
* test(tool-call): verify MCP images hoist out of collapsed tool group
* fix(tool-call): hoist all grouped attachments and prevent ExecuteCode double-render
- rename hideImageAttachments -> hideAttachments and hide every attachment
in the inner tool when a group auto-collapses, then hoist them via
ToolCallGroup with default variant 'all' so non-image attachments survive
the collapse alongside images
- thread hideAttachments to ExecuteCode so it skips its inline AttachmentGroup
when grouped, preventing double-render when the group is expanded
- memoize sequentialParts and groupedParts in ContentParts (with
groupAttachments rolled into each tool-group entry) so we don't re-flatMap
on every render
* test(tool-call): cover hideAttachments contract and grouping integration
- ToolCall: assert AttachmentGroup is skipped when hideAttachments=true and
rendered when explicitly false, locking the prop's contract
- ToolCallGroup: update variant assertion to 'all' (now hoists images and
files together) and add a non-image-only hoist case
- ContentParts.integration: new test exercising the full
ContentParts -> Part -> ToolCall -> AttachmentGroup chain with realistic
MCP-shaped data (groups 2+ contiguous tool calls and hoists, single calls
render inline, mixed image+file hoists, empty attachments are a no-op)
* fix(tool-call): extend hideAttachments to bash/read_file/skill/subagent
When the post-rebase dev branch added BashCall, ReadFileCall, SkillCall,
and SubagentCall as dedicated tool renderers, each rendered its own
inline AttachmentGroup. Once the parent tool group hoists every
attachment, those inline groups would double-render, so they now honor
the same hideAttachments contract as ToolCall and ExecuteCode.
Also seed the new ToolCallGroup mocks (Users icon, getToolDisplayLabel)
so the existing hoist test suite keeps passing on dev.
* fix(image-gen): suppress inline image when attachments are hoisted
OpenAIImageGen renders the generated image directly via <Image>. When
its tool_call lands inside a grouped tool call, the parent now hoists
those attachments into ToolCallGroup's AttachmentGroup, and the inline
<Image> would render the same file a second time. Thread hideAttachments
through Part -> ImageGen (agent-style branch) so the agent-style image
slot stays out of the way once the parent has hoisted.
* refactor(tool-call): drop dead variant prop and flatten render-part hooks
- AttachmentGroup's variant prop ('images' / 'non-images') had no callers
after the final hoisting design landed, so remove the prop and the
filtering branches; everything passes the default 'all' behavior.
- Replace the makeRenderPart factory + dual useMemo with two plain
useCallbacks (renderPart, renderGroupedPart) sharing the same dep set.
- Tighten test mocks: drop 'any' in the new integration test, hoist the
MCP delimiter constant above its consumer, and remove the now-stale
data-variant attribute assertion.
* refactor(tool-call): extract getToolCallId helper and tidy imports
- Pull the (part?.[TOOL_CALL] as Agents.ToolCall)?.id chain into a single
getToolCallId helper in ContentParts so the three call sites stop
repeating the cast verbatim.
- Re-sort ToolCallGroup local imports longest-to-shortest per the project
convention.
- Add a Users mock to the integration test's lucide-react stub so future
subagent-group tests don't trip over an undefined glyph.
* refactor(tool-call): unnest ternaries in subagent and group labels