fix(openai): map Responses refusal chunks during tool calls#101433
fix(openai): map Responses refusal chunks during tool calls#101433WOLIKIMCHENG wants to merge 2 commits into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 3:47 AM ET / 07:47 UTC. Summary PR surface: Source +91, Tests +135. Total +226 across 6 files. Reproducibility: Source-level yes: current main only handles response.refusal.delta for message items while function-call streams are finalized separately. A live OpenAI stream or captured SSE fixture for the exact sequence is still missing. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Preserve provider-refusal diagnostics through the shared and agent Responses lifecycle wrappers, cover those entrypoints with regression tests, then add redacted real Responses or captured-SSE proof before merge. Do we have a high-confidence way to reproduce the issue? Source-level yes: current main only handles response.refusal.delta for message items while function-call streams are finalized separately. A live OpenAI stream or captured SSE fixture for the exact sequence is still missing. Is this the best way to solve the issue? No: accumulating refusal deltas in the parser is a plausible layer, but the public stream lifecycle still turns parser-level errors into a generic thrown error. The narrower complete fix is to preserve the existing refusal message through the wrappers and test those entrypoints. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5c4b63964cc3. Label changesLabel justifications:
Evidence reviewedPR surface: Source +91, Tests +135. Total +226 across 6 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
|
Closing because the patch is based on an unproven and contract-invalid wire shape. Official refusal delta events identify message content with item, output, and content indexes; this branch omits that identity and attaches refusal to whichever function call is current. That can corrupt parallel tool calls, and the lifecycle wrapper still replaces the refusal with a generic error. Please reopen with a redacted real SSE capture and a current-main implementation routed by event identity. |
Fixes #101059
What Problem This Solves
Fixes an issue where users running OpenAI Responses models with strict tool usage would get a generic tool-call failure instead of a handled refusal when the provider emitted refusal chunks during a tool call stream.
Why This Change Was Made
This change teaches the OpenAI Responses stream parsers to treat refusal chunks that arrive while a function call is in progress as provider refusals instead of finishing an empty or partial tool call. The fix is intentionally narrow: it updates both the shared
@openclaw/aiprovider path and the agent transport sibling path, while preserving the existing stop-reason mapping for non-refusal completions.User Impact
Users now get a stable provider-refusal error instead of a cryptic tool-call parsing failure when an OpenAI Responses model refuses a tool call mid-stream. This keeps refusal handling consistent across the shared provider adapter and the agent transport path, and avoids surfacing a broken partial tool invocation as if it were a normal tool call.
Evidence
Focused local validation passed on the touched paths:
Results:
test/vitest/vitest.unit.config.ts:35 passedtest/vitest/vitest.agents.config.ts:296 passed[test] passed 2 Vitest shards in 56.22sAdditional checks:
git diff --checkpassedRegression coverage added for both stream parser paths:
provider_refusal