Summary
OpenAI Chat Completions responses that report a refusal in message.refusal can be treated as malformed output instead of a provider-declared refusal.
Reproduction
A Chat Completions response with finish_reason set to stop, message.content set to null, and message.refusal set to a refusal string passes through the shared result handler. The handler checks finish_reason, then returns message.content or an empty string; it does not inspect message.refusal. A minimal in-memory response reproduced an empty ProviderResult text without raising TypeSafeError.
Expected behavior
Raise TypeSafeError that identifies the refusal and preserve the response trace, without spending a malformed-output corrective retry.
Actual behavior
The refusal is reduced to empty text and enters malformed JSON validation. With corrective retries enabled, the adapter can retry a response the provider has already identified as a refusal; without retries, the caller receives a validation error rather than a refusal error.
Related coverage
Issue #43 handles Anthropic and OpenAI Responses refusals. Its description leaves Chat Completions aside because #38 covers finish_reason. Issue #38 rejects non-success finish reasons, but this response has finish_reason=stop and the refusal is in message.refusal, so the cases are distinct.
A focused regression should cover sync and async Chat Completions responses with message.refusal, including the no-corrective-retry behavior.
Summary
OpenAI Chat Completions responses that report a refusal in message.refusal can be treated as malformed output instead of a provider-declared refusal.
Reproduction
A Chat Completions response with finish_reason set to stop, message.content set to null, and message.refusal set to a refusal string passes through the shared result handler. The handler checks finish_reason, then returns message.content or an empty string; it does not inspect message.refusal. A minimal in-memory response reproduced an empty ProviderResult text without raising TypeSafeError.
Expected behavior
Raise TypeSafeError that identifies the refusal and preserve the response trace, without spending a malformed-output corrective retry.
Actual behavior
The refusal is reduced to empty text and enters malformed JSON validation. With corrective retries enabled, the adapter can retry a response the provider has already identified as a refusal; without retries, the caller receives a validation error rather than a refusal error.
Related coverage
Issue #43 handles Anthropic and OpenAI Responses refusals. Its description leaves Chat Completions aside because #38 covers finish_reason. Issue #38 rejects non-success finish reasons, but this response has finish_reason=stop and the refusal is in message.refusal, so the cases are distinct.
A focused regression should cover sync and async Chat Completions responses with message.refusal, including the no-corrective-retry behavior.