项目文件夹

文件
Ali Khokhar f3ea35a777 Consolidate OpenAI-compatible provider adapters (#1069)
## Problem

Sixteen OpenAI-compatible providers were represented by
configuration-only subclasses and one factory function each. The sole
provider family also lived under a multi-transport namespace that no
longer described the codebase, while provider defaults, IDs, and
instance caching passed through redundant forwarding layers.

## Changes

| Before | After |
| --- | --- |
| Sixteen provider IDs used configuration-only subclasses. | Immutable
OpenAI-chat profiles configure one concrete provider while preserving
each provider's request policy. |
| Every provider required a dedicated factory function. | Generic
profile construction is the default; only eight adapters with real state
or algorithms retain factories. |
| The sole provider family lived under `providers/transports/`. |
`providers/openai_chat/` directly owns shared request, stream, recovery,
tool, and usage behavior. |
| Provider defaults and IDs passed through forwarding modules, and
constructors repeated fallback resolution. | The neutral catalog
resolves complete immutable provider configuration once. |
| `ProviderRuntime` wrapped a pass-through provider cache. |
`ProviderRuntime` directly owns lazy provider instances and cleanup. |
| Production carried the duplicated adapter structure. | The final shape
removes a net 873 production lines with no compatibility shim or
customer-facing provider change. |

<!-- greptile_comment -->

<details open><summary><h3>Greptile Summary</h3></summary>

This PR consolidates OpenAI-compatible providers behind shared profiles
and runtime construction. The main changes are:

- OpenAI-chat behavior moved into `providers/openai_chat`.
- Configuration-only provider subclasses replaced by immutable profiles.
- Provider runtime now owns lazy instance caching and cleanup directly.
- Provider defaults and IDs now resolve through the neutral catalog.
- Tests and smoke helpers updated for the new provider shape.
</details>


<h3>Confidence Score: 5/5</h3>

This looks safe to merge.

No blocking issues found in the changed code.

No files need attention.

<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>

**What T-Rex did**
- T-Rex produced a proof for a posted P1 finding and linked it to the
corresponding review comment for details.
- T-Rex saved contract-validation logs for provider consolidation and
preserved four log files that show the test hanging boundary and the
timeout exit code 124.

<a
href="https://app.greptile.com/trex/runs/14125428/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a>

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>


<details open><summary><h3>Important Files Changed</h3></summary>




| Filename | Overview |
|----------|----------|
| src/free_claude_code/providers/runtime/config.py | Builds resolved
provider configuration from catalog descriptors, including static
credentials for local providers. |
| src/free_claude_code/providers/openai_chat/provider.py | Creates
shared OpenAI-chat clients from resolved configuration and immutable
provider profiles. |
| src/free_claude_code/providers/openai_chat/profiles.py | Defines
declarative profiles for formerly configuration-only OpenAI-compatible
adapters. |

</details>


<!-- greptile_failed_comments -->
<h3>Comments Outside Diff (2)</h3>

1. General comment 

<a href="#"><img alt="P1"
src="https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9"
align="top"></a> **NIM OpenAI-compatible 504 exhausted streaming retry
path hangs instead of completing with the expected user-facing error**

   - **Bug**
- The broader provider consolidation suite failed all parametrized
`test_nim_stream_openai_5xx_exhausted_emits_user_message` cases under
xdist with worker crashes. A serial isolation run narrowed this to the
same OpenAI-compatible NIM exhausted 5xx streaming path: 500, 502, and
503 completed, but the 504 case did not finish before the explicit 120s
timeout, producing exit code 124. This contradicts the expected
streaming retry contract that exhausted transient provider failures
terminate and emit the configured user-facing error.
   - **Cause**
- The OpenAI-compatible streaming retry/exhaustion path for NIM 504
responses appears to wait indefinitely or otherwise fail to terminate
after retries are exhausted. The exact code-level loop/await point was
not isolated within the validation budget, but the failure is anchored
to the provider streaming retry contract exercised by
`tests/providers/test_openai_compat_5xx_retry.py::test_nim_stream_openai_5xx_exhausted_emits_user_message[504-temporarily
unavailable]`.
   - **Fix**
- Inspect the OpenAI-compatible/NIM streaming retry exhaustion handling
for 504 responses and ensure retry limits are enforced, the async stream
is closed/cancelled on exhaustion, and the provider raises/emits the
same terminal user-facing error contract as the 500/502/503 cases. Add
or keep a serial regression test for the 504 exhausted stream path to
prevent hangs.

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>

2. General comment 

<a href="#"><img alt="P1"
src="https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9"
align="top"></a> **OpenAI-compatible 5xx retry suite hangs on exhausted
502 retry path**

   - **Bug**
- The recommended non-xdist retry validation does not complete. A
verbose rerun with a 60 second timeout shows the suite passes tests
through the exhausted 500 case, then times out while running
`test_nim_stream_openai_5xx_exhausted_emits_user_message[502-temporarily
unavailable]`. This indicates the consolidated OpenAI-compatible
provider retry/error path can hang for at least the 502 exhausted-error
scenario, preventing reliable validation and potentially blocking
callers from receiving the expected `ExecutionFailure`.
   - **Cause**
- The OpenAI-compatible provider's exhausted 502 retry/error handling
path appears not to terminate promptly under the mocked repeated
`openai.InternalServerError` scenario. The exact code location was not
changed during validation, but the failure is isolated to the shared
OpenAI-chat 5xx retry behavior exercised by
`NvidiaNimProvider.stream_response`.
   - **Fix**
- Debug the shared OpenAI-chat retry loop/error classification for 502
responses. Ensure retries are bounded, patched `asyncio.sleep` is
awaited without real backoff during tests, and exhausted 502/503/504
errors consistently raise `ExecutionFailure` with the expected
temporary-unavailable message instead of continuing work indefinitely.

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>

<!-- /greptile_failed_comments -->

<sub>Reviews (2): Last reviewed commit: ["Prove local provider
credential
resoluti..."](https://github.com/alishahryar1/free-claude-code/commit/ba226d97efdae1889bea0372c7bacf268122d65c)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43580983)</sub>

<!-- /greptile_comment -->
2026-07-11 19:55:19 -07:00

26 行
693 B
Python

"""Shared MagicMock request objects for OpenAI-compatible provider tests."""
from unittest.mock import MagicMock
def make_openai_compat_stream_request(
*, model: str = "test-model", stream: bool = True
) -> MagicMock:
"""Minimal request stub matching :meth:`OpenAIChatProvider._build_request_body` needs."""
req = MagicMock()
req.model = model
req.stream = stream
req.messages = []
req.system = None
req.tools = None
req.tool_choice = None
req.metadata = None
req.max_tokens = 4096
req.temperature = None
req.top_p = None
req.top_k = None
req.stop_sequences = None
req.extra_body = None
req.thinking = None
return req