Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f845e796ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const hasStreamed = (message: TMessage) => | ||
| (message.content?.length ?? 0) > 0 || (message.text?.length ?? 0) > 0; |
There was a problem hiding this comment.
Ignore placeholder-only parts when deriving status
When an Assistants turn starts, useChatFunctions seeds the response with an empty text part, and dual-conversation turns seed type: '' lane placeholders. Testing only content.length therefore reports streaming before any model output arrives, so consumers skip the submitted loading state. Treat empty text/reasoning/tool placeholders as non-streamed content; the existing isEmptyContentPart helper already defines these cases.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b49de6e. status reads streamed output through isEmptyContentPart, so empty text/think parts and type '' lane placeholders keep the turn submitted. Covered by 'stays submitted while the response holds only placeholder parts'. The lane placeholder also exposed a crash in toUIMessage, fixed on the base in 16aa523.
| const current = getMessages() ?? []; | ||
| const next = typeof update === 'function' ? update(current.map(toUIMessage)) : update; | ||
| const byId = new Map(current.map((message) => [message.messageId, message])); | ||
| setStoredMessages(next.map((message) => fromUIMessage(message, byId.get(message.id)))); | ||
| }, |
There was a problem hiding this comment.
Make setMessages updates reactive
When an updater changes only a non-tail message, this cache write does not cause useChat to re-render. The provider's React Query subscriptions select only the active tail and its ID, which remain referentially unchanged in that scenario, while this facade reads the full cache imperatively and owns no subscription. As a result, the returned messages remains stale until some unrelated context update occurs, breaking common operations such as editing an earlier message through setMessages; the facade needs to subscribe to the message cache or otherwise schedule an update after the write.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b49de6e. messages now reads getMessages through useSyncExternalStore, subscribed to message query cache events, so a setMessages write re-renders without an unrelated context change. No state is added. Covered by 're-reads messages when the message cache is written'.
| const part = message.content?.find((item) => item?.type === ContentTypes.ERROR); | ||
| return part?.type === ContentTypes.ERROR ? (part.error ?? '') : ''; |
There was a problem hiding this comment.
Read error text from Assistants error parts
When an Assistants run fails, both Assistants error handlers append a part shaped like { type: ContentTypes.ERROR, text: { value: message } }, without an error property or populated top-level message text. This expression consequently returns an empty string, so the facade reports status: 'error' but exposes new Error('') instead of the server's failure message. Extract the error part's text/TextData value as well as its legacy error field.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b49de6e. The error text falls back to the error part's error field, then its text (string or TextData value). Covered by 'reads the error text of an Assistants error part'.
| const byId = new Map(current.map((message) => [message.messageId, message])); | ||
| setStoredMessages(next.map((message) => fromUIMessage(message, byId.get(message.id)))); |
There was a problem hiding this comment.
Attach newly inserted UI messages to the conversation
When setMessages is used to append a valid UIMessage that was not produced by this facade, its optional metadata will commonly omit LibreChat's conversationId and parentMessageId. Because there is no matching base, fromUIMessage then stores both fields as null, creating a disconnected root that branch selection and regenerate({ messageId }) cannot operate on. Populate these fields from the active conversation and neighboring message when converting newly inserted messages, or explicitly reject insertion rather than accepting an orphaned message.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b49de6e. A message with no stored counterpart gets the active conversation's id and, unless its metadata names a parent, the previous message as parent. Covered by 'joins an inserted message to the conversation under the one before it'.
f845e79 to
6a8a16b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a8a16be2d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const message of stored ?? []) { | ||
| views.push(toUIMessage(message)); |
There was a problem hiding this comment.
Cache per-message UI conversions during streaming
When this hook is mounted under the active ChatView, every streamed cache write produces a new stored array and causes this loop to reconvert the entire transcript; toUIMessage also traverses each message's content, files, and attachments. Long conversations therefore incur O(total history) render work and allocations for every streamed update, rather than remapping only the changed tail, which can visibly degrade token streaming. Cache conversions by TMessage reference or select the mapped messages incrementally.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b49de6e. Views are cached in a WeakMap keyed by the stored TMessage, so a streamed cache write remaps only the messages it replaced. Covered by 'keeps unchanged message views across a streamed update', which asserts the untouched view is the same object.
6a8a16b to
b49de6e
Compare
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b49de6ee83
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let view = views.get(message); | ||
| if (!view) { | ||
| view = toUIMessage(message, mappingOptions); | ||
| views.set(message, view); |
There was a problem hiding this comment.
Invalidate cached views for mutable stream messages
On the second and later content frame for a response, this returns the first cached UIMessage instead of remapping its updated content. Fresh evidence against the prior caching resolution is in client/src/hooks/SSE/useContentHandler.ts:55-82: messageMap deliberately reuses the same response object and mutates response.content for each frame, so message identity does not change as the comment assumes. Consequently, consumers of this facade can remain stuck on the first streamed chunk until the final server message replaces the object; cache against a content/version identity or make the stream producer immutable.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in dc26613. Confirmed against useContentHandler: the response object is reused across frames and its content array replaced. The view cache now checks the fields a view reads (content, text, files, attachments, error, unfinished), not only the object, so a frame that replaces content remaps that response and unchanged messages keep their views. Covered by 'remaps a response whose content the stream replaced in place'.
| byToolCall = mapAttachments(message.attachments ?? []); | ||
| attachmentsByMessage.set(message, byToolCall); | ||
| } | ||
| const meta = getToolMeta({ type: ContentTypes.TOOL_CALL, tool_call: toolCall }, byToolCall); |
There was a problem hiding this comment.
Scope tool attachments by sibling step
When a message contains repeated provider tool-call IDs and the newer call has not received a stepId yet, calling getToolMeta without its sibling-step set lets filterAttachmentsForPart associate an older step's attachment with the new call. This is the exact repeated-ID case handled by summarizeSpan in outcome.ts: an earlier memory-error artifact or failed/cancelled background-status attachment can make the newer call appear as output-error even though it is still running. Derive the owned sibling step IDs from the message content and pass them as getToolMeta's third argument.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in dc26613. The resolver builds, per message snapshot, the step ids each provider tool-call id already owns, and passes them to getToolMeta as siblingStepIds for a call without a stepId, the way summarizeSpan does. Covered by 'keeps a stepless call clear of attachments its repeated id owns elsewhere', which fails without the fix.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc26613757
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const readMessages = useCallback(() => getMessages(), [getMessages]); | ||
| const stored = useSyncExternalStore(subscribe, readMessages, readMessages); |
There was a problem hiding this comment.
Version snapshots for in-place stream writes
On the second and subsequent useContentHandler frame, client/src/hooks/SSE/useContentHandler.ts:79-97 replaces response.content on the same response object and writes an array containing the same message references. React Query's structural sharing can therefore retain the existing outer array, and although the subscription fires, readMessages() returns the identical snapshot, so useSyncExternalStore suppresses the render before isSameSource can inspect the changed content. Consumers remain on the first chunk until a later immutable or final write. Fresh evidence beyond the earlier view-cache fix is the unchanged outer snapshot identity at this external-store boundary; expose a per-write version/query state or make the stream write immutable.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2254d21. Confirmed: the content handler replaces content on the same response object, so structural sharing hands back the same cached array and the array-identity snapshot suppressed the render. The external-store snapshot is now keyed by the message cache write count (successful updates and removals of messages queries), so every frame re-renders and the per-field view cache remaps the response. Regression: 'follows each stream frame written to the cache with the same message references' in facade.spec.tsx runs a real QueryClient, asserts the cached array is retained, and fails on dc26613.
| if (cached && isSameSource(cached, message)) { | ||
| return cached.view; | ||
| } | ||
| const view = toUIMessage(message, mappingOptions); |
There was a problem hiding this comment.
Handle empty tool-call slots before mapping
When a live/provider message contains an empty tool-call slot such as { type: 'tool_call' } or a null payload, this unconditional conversion throws because toToolPart passes undefined to readToolCall, which immediately reads .type. These slots are recognized transient input—isEmptyContentPart explicitly classifies tool calls missing their payload, while server compaction removes them only before persistence—so mounting the facade during such a stream can crash instead of returning an empty/step placeholder. Guard or normalize malformed tool calls before mapping, including in getToolContext.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed. The facade's own getToolContext loop now tolerates a missing tool_call (2254d21). The throw you traced sits in the base mapping (toToolPart/readToolCall, plus the agentId read in toUIMessage), which belongs to #16374 and is fixed there at 6a3685f with a parts.spec regression mapping the empty slot to step-start. It reaches this branch when #16374 merges and this stack rebases.
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2254d21e15
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (current?.writes === writes.current && current.read === getMessages) { | ||
| return current; |
There was a problem hiding this comment.
Read snapshot versions from the external store
When the message cache changes after readSnapshot() runs during render but before subscribe() installs its listener, that write never increments writes.current. React's post-subscribe consistency check then returns the cached snapshot from this branch despite getMessages() having changed, so a one-off load or setMessages call can remain stale until another message-cache event occurs. Fresh evidence after the prior versioning fix is that the version now lives only in the listener rather than in query state; read a store-owned update counter/version inside readSnapshot() instead.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 90b7b57 (on top of baa1583). The snapshot is keyed by the array getMessages() returns and by the dataUpdateCount of the queries holding it, a count React Query owns and bumps on every setQueryData even when structural sharing keeps the array. A write before the listener subscribes is therefore seen by React's post-subscribe check. Regression: 'reads messages a write the listener never saw replaced' in facade.spec.tsx.
| const base = byId.get(view.id); | ||
| const message = fromUIMessage(view, base); | ||
| if (!base) { | ||
| message.conversationId ??= conversationId; |
There was a problem hiding this comment.
Reject foreign conversation IDs on inserted messages
When a newly inserted UIMessage explicitly carries another chat's metadata.conversationId, this nullish assignment preserves that foreign ID instead of joining the active conversation. setStoredMessages then calls getMessageCacheIds, which uses the trailing concrete conversation ID and writes the entire mixed history into both cache keys, potentially making later sends or regenerations use another chat's messages. Fresh evidence beyond the earlier missing-metadata case is this cache-alias behavior; force new messages to the active conversation or reject foreign IDs.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in baa1583: a new message takes the active conversationId whenever there is one, so a foreign metadata.conversationId can no longer alias the history across cache keys. Regression: 'joins an inserted message that names another chat to the active conversation'.
| if (message.text) { | ||
| return message.text; |
There was a problem hiding this comment.
Prefer the error part over surviving response text
When a failed content-bearing response retains nonempty top-level message.text alongside a ContentTypes.ERROR part, this early return exposes the surviving response text as error.message instead of the actual failure. Such mixed rows are valid—the message mapping deliberately preserves a stored top-level summary that differs from its content parts—and failed agent runs can retain output before an appended error. Fresh evidence beyond the earlier empty-text error fix is this nonempty stored-text case; read the error part first and use message.text only for legacy message.error rows without one.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in baa1583: getErrorText reads the error part first and falls back to message.text only for legacy rows without one. Regression: 'reports the error part rather than text the failed response kept'.
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 686d6b797b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const stored = getMessages(); | ||
| let writes = 0; | ||
| if (stored) { | ||
| for (const query of queryClient.getQueryCache().findAll({ queryKey: [QueryKeys.messages] })) { |
There was a problem hiding this comment.
Scope snapshot versioning to the active message query
When this facade is mounted during generation, each content frame written by useContentHandler.ts:97 triggers readSnapshot, and this findAll call scans the entire React Query cache to locate the active array by identity; React can also read the snapshot multiple times for one notification. After navigation has cached many conversations—or with both chat panes mounted—every streamed frame therefore performs repeated O(number of cached queries) work, undermining the per-message conversion cache and potentially making token streaming janky. Carry the active query key/version through the contract or retain the matching query rather than scanning all cached message queries for every snapshot read.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 4704124. The holder queries are looked up once per cached array and retained while getMessages() keeps returning it; a stream frame keeps the array (that was the original structural-sharing case), so frames only re-read the retained queries' dataUpdateCount instead of scanning the cache. A new array (new message, conversation switch, removal) triggers one lookup. Regression: the in-place frame test now spies on queryCache.findAll and asserts no call across a streamed frame; it fails on 686d6b7.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4704124c2c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (view.metadata?.parentMessageId === undefined) { | ||
| message.parentMessageId = previousId; |
There was a problem hiding this comment.
Parent appended messages to the active branch
When the user has selected an older response sibling while a newer sibling remains last in the flat message cache, appending a metadata-free UIMessage here assigns the newer hidden sibling as its parent because previousId follows cache-array order rather than latestMessageId. The inserted message therefore lands on the wrong branch and is not visible beneath the currently selected response; derive the default parent from the active branch tail instead.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid on 4704124. Same root as the two sibling threads from this round: the facade has to reconstruct what the ChatContext contract does not expose (the active branch, the message query identity, a first-delta signal). Held open while that is decided at the contract level rather than patched per case in the facade.
There was a problem hiding this comment.
Fixed in 22121d1 (head e88c1c8). setMessages walks the active branch up from the contract's latestMessageId; a new message follows the message before it only when that one is new or on the active branch, otherwise it joins the active branch's tail. Regression: 'parents an appended message to the active branch, not a hidden sibling'.
| ); | ||
|
|
||
| return { | ||
| id: conversation?.conversationId ?? undefined, |
There was a problem hiding this comment.
Derive the facade ID from the active message query
During navigation, useChatHelpers intentionally reads messages using the URL paramId, while the conversation atom can temporarily still describe the previous route (the existing guard in ChatView.tsx:95-98 accounts for this lag). In that state this hook returns conversation B's messages but reports conversation A as id, so a consumer can associate the newly displayed transcript and subsequent actions with the wrong chat; expose the query/route conversation ID or suppress the facade until the identities match.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid on 4704124. Same root as the two sibling threads from this round: the facade has to reconstruct what the ChatContext contract does not expose (the active branch, the message query identity, a first-delta signal). Held open while that is decided at the contract level rather than patched per case in the facade.
There was a problem hiding this comment.
Fixed in 22121d1 over #16395, which adds messagesKey (the key getMessages() reads, the route id) to the chat contract; the facade's id is that key and it subscribes to exactly that query. Regressions: 'reports the chat the messages are read from while the conversation catches up' here and the host-side contract.spec in #16395.
4704124 to
fc04eb6
Compare
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc04eb6dd8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const message = fromUIMessage(view, base); | ||
| if (!base) { | ||
| message.conversationId = conversationId ?? message.conversationId; |
There was a problem hiding this comment.
Keep existing messages in the active conversation
When an updater changes metadata.conversationId on an existing message—particularly the tail—fromUIMessage(view, base) preserves that explicit foreign ID because normalization only runs for !base. useChatHelpers.setMessages then derives cache IDs from the resulting messages in client/src/hooks/Chat/cache.ts, causing the active history to be written under the foreign conversation's cache key as well. Fresh evidence beyond the prior insertion fix is this base-backed path; reject mismatched IDs or force the active conversation ID for existing messages too.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b6d54e8: setMessages writes every existing message back under the conversation it is stored in, whatever its view's metadata says, so a foreign id cannot alias the history into another cache key; new messages take the active chat's id. Regression: 'keeps a stored message in its conversation when its view names another'.
fc04eb6 to
e88c1c8
Compare
💡 Codex ReviewLibreChat/client/src/hooks/Chat/useChatHelpers.ts Lines 153 to 154 in e88c1c8 Replacing the setter-only access with AGENTS.md reference: AGENTS.md:L102-L108 LibreChat/client/src/hooks/Chat/facade.ts Line 216 in e88c1c8 When a non-Assistants request leaves a latest row with AGENTS.md reference: AGENTS.md:L31-L33 LibreChat/client/src/hooks/Chat/facade.ts Line 292 in e88c1c8 When the user navigates from an in-flight conversation A to conversation B, AGENTS.md reference: AGENTS.md:L31-L33 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6d54e8fe9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? 'streaming' | ||
| : 'submitted'; | ||
| } | ||
| return latest && !latest.isCreatedByUser && isErrorMessage(latest) ? 'error' : 'ready'; |
There was a problem hiding this comment.
Report user-authored error tails
When loading a conversation whose active branch ends in a failed user message (error: true), this authorship check forces status to ready and leaves error undefined. That is a supported transcript state—useTextarea.ts:116-117 explicitly detects an errored user-authored tail—so facade consumers miss the failure on restored sessions; apply isErrorMessage regardless of authorship once submission has settled.
AGENTS.md reference: AGENTS.md:L31-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c2b6120: once the turn settles, status is error for any failed tail, a user message that never got a response included; its error text comes from an error part and is empty otherwise, since a user message's text is what the user sent. Regression: 'reports a failed user message at the tail as an error'.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2b6120f27
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d50209c54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures. |
3d50209 to
def2d0e
Compare
useChat reads ChatContext and presents it as @ai-sdk/react@4.0.117 does: UI-shaped messages, a submitted/streaming/ready/error status derived from the in-flight flag and the latest message, and sendMessage, regenerate, stop and setMessages forwarding to the contract. It holds no state and changes no existing consumer.
The parts mapping reads only the markers stored on a tool call; the facade supplies getToolMeta as its resolveToolFailure, so memory failure prose and background task status attachments reach the UI parts view the same way they reach the tool cards.
…er message messages re-reads getMessages whenever the message query cache is written, through useSyncExternalStore, so setMessages and other cache writes show without an unrelated context update. Views are cached per message reference, so a streamed chunk remaps only the messages it replaced. status ignores placeholder parts through isEmptyContentPart, the error reads an Assistants error part's text, and a message inserted through setMessages joins the active conversation under the message before it.
…cade The content handler reuses a response object across frames and replaces its content, so views are cached per message and per snapshot of the fields a view reads rather than by the object alone. The tool outcome resolver passes getToolMeta the step ids a repeated provider id already owns, so a call without a step is not failed by another step's attachment.
… in the facade The content handler replaces a response's content on the same object, so structural sharing keeps the cached message array and the external store saw no change. The snapshot is now keyed by the message cache write count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…nd report the error part Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…ntract The facade subscribes to the one message query the contract names instead of matching cached arrays, reports that query's conversation as the chat id, counts only parts the submitted response was not seeded with as streamed output, and parents an appended message to the active branch. A memory call's failure prose is its error text now that the resolver's reason becomes the part's errorText. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…iles as streamed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…rors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…n metadata changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
…as no submitted response Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQdLgSaTheiBFHYnUDgobw
def2d0e to
36d05d2
Compare
Summary
The chat context is already an explicit contract, but consumers still read it in LibreChat terms (
ask,isSubmitting,getMessages). This addsuseChatinclient/src/hooks/Chat/facade.ts, which presents the same contract the way@ai-sdk/react@4.0.117does. It maps messages through the parts mapping #16374 landed on canary.It returns
id(the contract'smessagesKey, the conversation the messages are read from),messages(getMessages()mapped throughtoUIMessage, re-read on every write to that message query),status,error,sendMessage,regenerate,stopandsetMessages.statusissubmittedwhile a turn is in flight and its response holds nothing beyond placeholders, thenstreaming; a rerun edit readsstreamingfrom its retained prefix, since the contract has no first-delta signal that a restored or replaced run could keep correct. Once the turn ends it iserrorif the latest message failed andreadyotherwise, which includes a stopped turn.abortScrollonly holds the scroll position, so a stop is read from the settled message instead.sendMessageisaskitself,stopisstopGenerating,regenerate({ messageId })resolves the target the contract expects, andsetMessageswrites UI messages back onto the stored ones, joining a new message to the active chat under the message before it, or under the active branch's tail when that message sits on another branch. The hook holds no state and reads no store, and no existing consumer changes.Depends on #16395, which adds
messagesKeyto the contract and which this is stacked on.Type of change
Testing
Tested environments/configuration:
Unit level only; no component uses the facade yet.
Automated tests:
client/src/hooks/Chat/__tests__/facade.spec.tsx: renders under the realChatContextwith a stubbed contract; status through submit, stream, finish, abort and error;sendMessageforwards toaskwith the same arguments;regenerate,stopandsetMessagesforward; in-place stream frames against a real QueryClient; the chat id during navigation; branch parenting of inserted messages; failed user tailscd client && npx jest src/hooks/Chat: 386 passedcd client && npm run typecheck: cleanreviewctl precheckagainstberry-13/chat-contract: static checks and related jest passScreenshots / recordings
No user-facing change.
Risk / compatibility
None.
messagesfollowsgetMessages(), so it is only as fresh as the host's re-render, the same as the contract it reads.Checklist