Skip to content

🗳️ feat: Approval-Responded, Output-Denied and Ask-User Parts in the UI Message View - #16961

Merged
berry-13 merged 4 commits into
devfrom
berry-13/chat-part-states
Oct 12, 2026
Merged

berry-13 merged 4 commits into
devfrom
berry-13/chat-part-states

Conversation

@berry-13

Copy link
Copy Markdown
Collaborator

Summary

The UIMessage view in librechat-data-provider (the parts projection the chat facade hands to useChat consumers) covered 5 of the AI SDK's 7 tool states and had no part for an ask-user pause: a reviewed tool call could only read as approval-requested, and the client-written ask_user_question content part mapped to step-start, so fromUIMessage dropped it and left a hole. That keeps the facade surface short of useChat parity for approvals.

Tool parts now follow ai@7.0.137: a call whose stored approval carries a decision reads as approval-responded (with approved and reason), a denied call that ended reads as output-denied, and an approved call keeps its approval through output-available or output-error. The decision is a new optional decision/reason pair on the stored approval; nothing writes it yet, so current messages map exactly as before. The ask-user pause becomes a data-ask-user-question part that keeps its content slot and round-trips losslessly, and its type moves into data-provider so the client reuses it. The pending_action transport comment now names both mappings.

Type of change

  • Feature

Testing

Tested environments/configuration:

Unit level only; no runtime surface changes, since no producer writes decision yet and the ask part renders from the same content as before.

Automated tests:

  • packages/data-provider: npx jest (68 suites, 2788 passed), including new parts.spec.ts cases: each decision kind as approval-responded, output-denied from an output or a failed step, approved calls through output and failure, the unchanged undecided-approval case, hand-built parts rebuilt without a base, and the ask pause through toUIPart, toUIParts (slot kept) and toUIMessage/fromUIMessage with and without a base, all toStrictEqual.
  • client: facade.spec, transport.spec, approval.spec, LiveParity, ToolCallGroup, useStepHandler.spec (6 suites, 422 passed); tsc --noEmit on client and packages/data-provider clean.

Screenshots / recordings

No user-facing change.

Risk / compatibility

Agents.ToolCall.approval gains two optional fields; stored messages and existing producers are unaffected.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • Required documentation PR: N/A

Copilot AI balanced review requested due to automatic review settings October 10, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@berry-13
berry-13 marked this pull request as ready for review October 10, 2026 14:30
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T07:38:34.211310Z 8d12994 New commits
🔒 Security Review ✅ Completed 2026-10-10T14:34:10.330373Z b0f9650 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0f9650711

ℹ️ 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".

Comment thread packages/data-provider/src/parts.ts Outdated
Comment on lines +507 to +508
if (approval?.approved === false && (ended || failure)) {
state = 'output-denied';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve output from respond decisions

When a completed respond decision carries the user's response in tool_call.output—the resume contract explicitly produces this shape in api/server/controllers/agents/__tests__/resume.spec.js:4265-4271—this branch classifies it as output-denied, and line 527 consequently omits the output. The resulting UIMessage loses the human-supplied tool result, leaving facade/useChat consumers unable to render or serialize it; distinguish respond from reject and preserve its output.

AGENTS.md reference: AGENTS.md:L49-L52

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4f326dc (next push): an ended respond decision now reads output-available and keeps the reviewer's output, covered by the new respond case in parts.spec.ts.

Comment on lines +801 to +802
if (part.type === 'data-ask-user-question') {
return { type: ASK_USER_QUESTION, ...part.data } as unknown as TMessageContentParts;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include ask-user parts in the stored content union

For any TypeScript consumer that round-trips data-ask-user-question, this function returns an ask_user_question object while declaring it as TMessageContentParts, whose union does not contain that discriminator. Callers therefore cannot safely narrow or construct TMessage.content with the newly exported part type, and the client still needs unknown casts to use it. Move the shared part shape into the content types and include it in TMessageContentParts rather than hiding the mismatch behind this cast.

AGENTS.md reference: AGENTS.md:L174-L179

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred to berry-13#320: the synthetic ask part already lives in TMessage.content outside the union on dev (client/src/utils/approval.ts), the round trip is lossless at runtime, and widening TMessageContentParts reaches every content switch in the client, so it gets its own change.

@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the current PR head 4f326dc. State the exact reviewed commit and ignore findings that apply only to earlier heads.

Purpose supplied by the requester: Fix respond decision output (Codex 4237960234)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f326dcec7

ℹ️ 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 { actionId, description } = toolCall.approval;
return { id: actionId, ...(description && { requestReason: description }) };
const { actionId, description, decision, reason } = toolCall.approval;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist decisions before deriving approval states

At reviewed head 4f326dcec76afa3831268d83327d06da51bafea2, production tool calls never populate this new decision field: the resume contract persists only the human-authored output (api/server/controllers/agents/__tests__/resume.spec.js:4265-4278), and the final SSE merge rebuilds the tool call without preserving approval (client/src/hooks/SSE/steps/content.ts:319-336). Thus decision is undefined here for real resumed calls, so a rejected completion is still classified as output-available and the new approval-responded/output-denied states are reachable only in the synthetic tests; carry the submitted decision into the resumed tool-call record before deriving these states.

AGENTS.md reference: AGENTS.md:L49-L52

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct that no producer writes the decision yet; without it the mapping is unchanged from dev, so nothing regresses. Persisting it spans the client stamp, the SSE merge and the server resume, so it is tracked in berry-13#321.

Comment thread packages/data-provider/src/parts.ts Outdated
Comment on lines +508 to +511
const responded = 'name' in toolCall && toolCall.approval?.decision === 'respond';
if (approval?.approved === false && !responded && (ended || failure)) {
state = 'output-denied';
} else if (failure) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat respond output as successful content

When a reviewer supplies text matching the legacy failure heuristic—such as Error: tool call failed: ... or a value beginning Error processing tool—resolveFailure marks the human response as failed before this branch, so a completed respond decision becomes output-error and its text is moved to errorText instead of remaining the successful substitute tool output. The agents SDK's respond path constructs a successful tool result from arbitrary reviewer text, so the respond exception must also bypass output-text/caller failure inference unless the call carries an explicit operational failure marker.

AGENTS.md reference: AGENTS.md:L49-L52

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3f1e324: a respond decision now uses only the explicit failure markers (failed or cancelled step, background cancel, input validation) and skips the output heuristic and the caller resolver; parts.spec covers it and fails without the fix.

@berry-13
berry-13 force-pushed the berry-13/chat-part-states branch from 3f1e324 to 8d12994 Compare October 12, 2026 07:33
@berry-13
berry-13 merged commit 8829a39 into dev Oct 12, 2026
58 of 59 checks passed
@berry-13
berry-13 deleted the berry-13/chat-part-states branch October 12, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants