项目文件夹

文件
Ali Khokhar f8c21a48f2 Introduce a typed application boundary for provider execution (#1045)
## Problem

The HTTP adapter owned model routing, provider execution, and
runtime-facing contracts, so API handlers depended on provider
implementation types. Provider preflight also discovered private request
builders dynamically, obscuring the boundary that must fail before
streaming begins.

## Changes

| Before | After |
| --- | --- |
| `api/` owned model routing and shared provider execution. |
`application/` owns routing and a settings-independent
`ProviderExecutor`. |
| API handlers accepted `BaseProvider` callbacks. | API handlers consume
the narrow structural `ProviderPort`. |
| `BaseProvider` discovered `_build_request_body` dynamically. | Both
transport families implement explicit abstract preflight, with LM Studio
composing context validation. |
| Request leases, task control, and provider model metadata had
adapter/provider owners. | Application-owned ports and immutable values
define those cross-package contracts. |
| Boundary direction was implicit. | Architecture contracts and
documentation enforce the final dependency direction. |
| Package version was `3.4.19`. | Package version is `3.4.20`, with the
lockfile updated. |
| Coverage followed the old module layout. | Deterministic
boundary/preflight regressions and live Messages/Responses smokes cover
the new shape. |

<!-- greptile_comment -->

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

This PR adds a typed application boundary for provider execution. The
main changes are:

- New `application` package for routing, execution, ports, and model
metadata.
- API handlers now call application-owned routing and provider
execution.
- Provider preflight is now explicit on the transport families.
- Runtime API composition now uses a task-control port for `/stop`.
- Import-boundary tests, smoke references, docs, version, and lockfile
were updated.
</details>

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

This looks safe to merge.

No blocking issues found in the changed code.

None.

<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**
- Ran a deterministic pytest run for the provider boundary preflight,
which completed with exit code 0 and 102 tests passing in 4.77 seconds.
- Launched the environment presence check as part of the preflight,
which completed with exit code 0 and confirmed
OPENCODE\_API\_KEY=\[REDACTED\] matched.
- Attempted the live provider smoke test, which completed with exit code
0 and 2 tests skipped due to incomplete smoke configuration.

<a
href="https://app.greptile.com/trex/runs/14067905/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/application/execution.py | Moves shared provider
execution into the application layer and keeps eager preflight before
token counting and streaming. |
| src/free_claude_code/application/ports.py | Adds structural provider,
request-runtime, and task-control protocols used across the new
boundary. |
| src/free_claude_code/api/routes.py | Updates route composition to use
the application provider resolver and task-control stop path. |
| src/free_claude_code/providers/base.py | Makes provider preflight
explicit by requiring subclasses or transport bases to implement it. |
| src/free_claude_code/providers/transports/openai_chat/transport.py |
Adds OpenAI-chat preflight through the same request-body builder used by
streaming. |
|
src/free_claude_code/providers/transports/anthropic_messages/transport.py
| Adds native Messages preflight through the native request-body
builder. |
| src/free_claude_code/providers/model_listing.py | Keeps provider
model-list parsing while moving `ProviderModelInfo` ownership to the
application layer. |
| src/free_claude_code/runtime/bootstrap.py | Passes the runtime object
through the new `tasks` service slot. |
| tests/contracts/test_import_boundaries.py | Extends import-boundary
tests for the new application package. |

</details>

<details open><summary><h3>Flowchart</h3></summary>

<a href="#gh-light-mode-only">

```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  API[api handlers and routes] --> Routing[application.routing]
  API --> Executor[application.execution]
  API --> Ports[application.ports]
  Executor --> ProviderPort[ProviderPort]
  ProviderPort --> Preflight[preflight_stream]
  ProviderPort --> Stream[stream_response]
  Runtime[runtime bootstrap and provider manager] --> Ports
  Providers[providers] --> Metadata[application.model_metadata]
  Executor --> Core[core anthropic and trace]
  Routing --> Config[config settings and model refs]
```

</a>
<a href="#gh-dark-mode-only">

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart LR
  API[api handlers and routes] --> Routing[application.routing]
  API --> Executor[application.execution]
  API --> Ports[application.ports]
  Executor --> ProviderPort[ProviderPort]
  ProviderPort --> Preflight[preflight_stream]
  ProviderPort --> Stream[stream_response]
  Runtime[runtime bootstrap and provider manager] --> Ports
  Providers[providers] --> Metadata[application.model_metadata]
  Executor --> Core[core anthropic and trace]
  Routing --> Config[config settings and model refs]
```

</a>
</details>

<sub>Reviews (1): Last reviewed commit: ["Introduce typed application
boundary"](https://github.com/alishahryar1/free-claude-code/commit/4cdcf97231c812c6f568ca3d74af7ce759d7f2dc)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43462788)</sub>

<!-- /greptile_comment -->
2026-07-10 20:06:57 -07:00

62 行
2.0 KiB
Python

"""Explicit test composition for the API adapter."""
from collections.abc import MutableMapping
from fastapi import FastAPI
from free_claude_code.api.app import create_app
from free_claude_code.api.ports import ApiServices
from free_claude_code.config.settings import Settings
from free_claude_code.providers.base import BaseProvider
from free_claude_code.providers.runtime import ProviderRuntime
from free_claude_code.runtime.application import ApplicationRuntime, RestartCallback
from free_claude_code.runtime.provider_manager import ProviderRuntimeManager
def create_test_app(
settings: Settings | None = None,
*,
providers: MutableMapping[str, BaseProvider] | None = None,
restart_callback: RestartCallback | None = None,
) -> FastAPI:
"""Build an API app with explicit in-memory runtime services."""
settings = settings or Settings()
if providers is None:
manager = ProviderRuntimeManager(settings)
else:
manager = ProviderRuntimeManager(
settings,
runtime_factory=lambda snapshot: ProviderRuntime(
snapshot,
dict(providers),
),
)
runtime = ApplicationRuntime(
manager,
transcriber=None,
restart_callback=restart_callback,
)
return create_app(
ApiServices(
requests=manager,
admin=runtime,
tasks=runtime,
)
)
def runtime_for_app(app: FastAPI) -> ApplicationRuntime:
"""Return the runtime supplied by :func:`create_test_app`."""
runtime = app.state.services.admin
if not isinstance(runtime, ApplicationRuntime):
raise TypeError("Test app does not use ApplicationRuntime")
return runtime
def provider_manager_for_app(app: FastAPI) -> ProviderRuntimeManager:
"""Return the provider manager supplied by :func:`create_test_app`."""
manager = app.state.services.requests
if not isinstance(manager, ProviderRuntimeManager):
raise TypeError("Test app does not use ProviderRuntimeManager")
return manager