文件历史

3 次代码提交

作者 SHA1 备注 提交日期
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
Ali Khokhar 4951983b5e Replace global runtime resources with explicit ownership (#1042)
## Problem

Provider, messaging, and transcription resources relied on
process-global state, leaving replacement, cancellation, and shutdown
ownership ambiguous. Separate server lifetimes could share
event-loop-bound resources or retain failed cleanup work.

## Changes

| Before | After |
| --- | --- |
| Provider clients found limiters through global singleton and scoped
registries. | Each provider instance receives and owns one explicitly
constructed limiter. |
| Messaging queues and voice pipelines relied on singleton or
module-global state. | Each platform owns its limiter and outbox, while
the application owns one injected transcriber. |
| Messaging shutdown mixed ingress, active work, delivery, and SDK
cleanup. | Application shutdown quiesces ingress, drains work, closes
delivery, then releases transcription and providers. |
| Cancelled or failed provider cleanup could be forgotten or treated as
complete. | The provider manager retains shielded generation and
unpublished-runtime cleanup until it succeeds. |
| Discord and Telegram startup tasks could outlive or poison runtime
readiness. | Platform runtimes observe long-lived tasks and retry only
independently repeatable lifecycle steps. |
| Constructor-captured security and diagnostic settings appeared
hot-applicable. | Admin marks those settings restart-required so applied
policy matches the running resource graph. |
| Lifecycle races lacked direct ownership coverage. | Deterministic
cancellation, retry, isolation, teardown, and live smoke contracts
protect the final ownership model. |

<!-- greptile_comment -->

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

This PR moves runtime resources from global state into explicitly owned
application objects. The main changes are:

- Provider generations own their rate limiters and cleanup tasks.
- Messaging platforms own their limiter, outbox, ingress, and delivery
lifecycle.
- Application shutdown now runs through ordered cleanup gates.
- Voice transcription is injected as an owned runtime resource.
- Admin config marks constructor-captured settings as restart-required.
</details>

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

The shutdown path needs a bounded cleanup result before merging.

Cleanup steps that hang never reach the retryable incomplete-shutdown
path. ASGI shutdown can remain stuck while waiting for an external SDK,
transcriber, workflow, or provider cleanup. The retry ownership model
works only after cleanup returns or raises.

src/free_claude_code/runtime/application.py

<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 ran the requested verification, but its local artifact
references were not uploaded.
- The validation run completed successfully with EXIT\_CODE: 0 and 62
tests passed in 3.91 seconds, using the command uv run pytest -vv
tests/runtime/test\_application\_runtime.py
tests/runtime/test\_provider\_manager.py
tests/providers/test\_provider\_runtime.py.

<a
href="https://app.greptile.com/trex/runs/14064214/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/runtime/application.py | Refactors shutdown into
ordered retryable cleanup gates, but cleanup awaitables can still block
shutdown forever. |
| src/free_claude_code/runtime/asgi.py | Reports incomplete runtime
shutdown when `close()` returns false. |
| src/free_claude_code/runtime/provider_manager.py | Adds owned provider
cleanup retry state and shielded generation cleanup. |

</details>

<a
href="https://app.greptile.com/api/ide/codex?prompt=IMPORTANT%3A%20Work%20in%20the%20repository%20%22alishahryar1%2Ffree-claude-code%22%20on%20the%20existing%20branch%20%22refactor%2Fruntime-owned-resources%22.%20Checkout%20that%20branch%20%E2%80%94%20do%20NOT%20create%20a%20new%20branch%20or%20open%20a%20new%20PR.%20Push%20your%20changes%20to%20%22refactor%2Fruntime-owned-resources%22.%0A%0AFix%20the%20following%201%20code%20review%20issue.%20Work%20through%20them%20one%20at%20a%20time%2C%20proposing%20concise%20fixes.%0A%0A---%0A%0A%23%23%23%20Issue%201%20of%201%0Asrc%2Ffree_claude_code%2Fruntime%2Fapplication.py%3A59%0A**Cleanup%20Await%20Blocks%20Shutdown**%0A%0AWhen%20a%20platform%20SDK%20stop%2C%20workflow%20drain%2C%20transcriber%20close%2C%20or%20provider%20cleanup%20hangs%2C%20this%20helper%20waits%20forever%20and%20never%20returns%20%60False%60.%20ASGI%20shutdown%20stays%20stuck%20in%20%60runtime.close%28%29%60%20instead%20of%20reporting%20an%20incomplete%20shutdown%2C%20so%20the%20retained%20resource%20graph%20cannot%20be%20retried%20cleanly.%0A%0A&repo=alishahryar1%2Ffree-claude-code&pr=1042&platform=github"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCodexDark.svg?v=6"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCodex.svg?v=6"><img
alt="Fix All in Codex"
src="https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCodex.svg?v=6"></picture></a>

<sub>Reviews (2): Last reviewed commit: ["Report incomplete runtime
shutdown to
AS..."](https://github.com/alishahryar1/free-claude-code/commit/338b2bd179c3875b15bbd52818dd04c780e5d46d)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43454593)</sub>

> Greptile also left **1 inline comment** on this PR.

**Context used:**

- Context used - CLAUDE.md
([source](https://app.greptile.com/alishahryar1/github/Alishahryar1/free-claude-code/-/custom-context?memory=d2fd24d8-0dec-4faf-8ee4-e085e215a2f8))

<!-- /greptile_comment -->
2026-07-10 18:46:50 -07:00
Ali Khokhar 160d63370b Establish single-owner runtime with stream-safe provider hot swaps (#1036)
## Problem

Provider runtime ownership was split between lifecycle code and mutable
FastAPI state, so Admin replacements could leak the new runtime,
double-close the old runtime, or close providers still serving active
streams. The API package also owned concrete process composition,
obscuring subsystem boundaries.

## Changes

| Before | After |
| --- | --- |
| FastAPI routes inspected several concrete `app.state` resources. |
FastAPI receives one explicit `ApiServices` boundary and stores only
`app.state.services`. |
| Admin Apply persisted config and directly replaced one runtime
reference. | Admin Apply validates a candidate, commits atomically, and
publishes it through the single runtime owner. |
| Provider replacement could close clients used by active streams. |
Generation leases retain old providers until each streaming or
non-streaming response finishes. |
| Provider generations owned discovery state and model metadata. |
`ProviderRuntimeManager` owns one application-lifetime catalog and one
discovery task across replacements. |
| API modules composed provider, messaging, and managed CLI resources. |
`runtime.bootstrap` composes concrete subsystems and
`ApplicationRuntime` owns their lifecycle. |
| Admin config, server URLs, and gateway model IDs lived under the API
package. | Admin config lives under `config`, server URLs live under
`config`, and gateway IDs live under `core`. |
| Messaging restoration and shutdown persistence were coordinated
externally. | `MessagingWorkflow` owns snapshot restoration and final
persistence flushing. |
| Hot-swap behavior lacked a real process-level race scenario. |
Deterministic ownership tests and a credential-free subprocess smoke
hold provider A while new requests switch to provider B. |
| Package version was `3.4.16`. | Package version is `3.4.17` with an
updated lockfile. |

<!-- greptile_comment -->

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

This PR centralizes server runtime ownership and provider hot swaps. The
main changes are:

- Adds `ApplicationRuntime` and `ProviderRuntimeManager` as the process
owners.
- Moves FastAPI to an explicit `ApiServices` boundary.
- Retains provider generations until request and stream responses
finish.
- Moves admin config, server URL, and gateway model ID modules to
neutral package owners.
- Updates admin apply to validate, persist, and publish provider-only
changes through the runtime owner.
- Adds runtime ownership tests and a credential-free smoke scenario.
</details>

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

This looks safe to merge.

No blocking issues found in the changed code. The provider lease path
releases resources on normal completion, stream close, and cancellation.
The admin apply path keeps restart-required changes separate from
provider-only hot swaps, and repository import paths appear updated for
the moved modules.

No files need follow-up 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**
- Ran the T-Rex smoke test command and confirmed it completed with exit
code 0 and pytest passing.
- Monitored runtime ownership activity during the run, including a
provider A stream request on model-a generation 1, an admin publish to
generation 2, a new request on model-b generation 2, and the completion
of the original generation 1 stream.
- Collected smoke result artifacts from the .smoke-results area for
gateway 1, gateway 0, and main, and made them available for review.
- Opened the smoke report JSON artifacts to review the summarized
outcomes for each target environment.

<a
href="https://app.greptile.com/trex/runs/14008910/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/runtime/provider_manager.py | Adds provider
generation ownership, request leases, replacement, discovery refresh,
and shutdown cleanup. |
| src/free_claude_code/runtime/application.py | Adds the process-level
owner for startup, shutdown, admin operations, messaging, and session
control. |
| src/free_claude_code/api/routes.py | Routes now acquire provider
generation leases and bind them to response lifetime. |
| src/free_claude_code/api/response_streams.py | Adds response lifetime
binding so retained resources release after stream completion,
cancellation, or close. |
| src/free_claude_code/config/admin/persistence.py | Moves admin config
persistence into `config` and adds prepared validation plus atomic
managed-env commits. |
| src/free_claude_code/runtime/bootstrap.py | Adds the production
composition root for logging, runtime owners, services, and ASGI wiring.
|
| src/free_claude_code/api/__init__.py | Removes package-level API
re-exports as part of the HTTP adapter boundary cleanup. |

</details>

<details open><summary><h3>Sequence Diagram</h3></summary>

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

```mermaid
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant API as FastAPI Route
participant Manager as ProviderRuntimeManager
participant Lease as Generation Lease
participant Runtime as Provider Generation
participant Admin as Admin Apply

Client->>API: Request /v1/messages or /v1/responses
API->>Manager: acquire()
Manager-->>API: lease for current generation
API->>Lease: resolve_provider()
Lease->>Runtime: use provider instance
Runtime-->>Client: response body or stream

Admin->>Manager: replace(candidate settings)
Manager->>Manager: publish new generation
Manager->>Manager: retire old generation

Client-->>API: response completes or disconnects
API->>Lease: release()
Lease->>Manager: decrement active leases
Manager->>Runtime: cleanup retired generation when drained
```

</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"}}}%%
sequenceDiagram
participant Client
participant API as FastAPI Route
participant Manager as ProviderRuntimeManager
participant Lease as Generation Lease
participant Runtime as Provider Generation
participant Admin as Admin Apply

Client->>API: Request /v1/messages or /v1/responses
API->>Manager: acquire()
Manager-->>API: lease for current generation
API->>Lease: resolve_provider()
Lease->>Runtime: use provider instance
Runtime-->>Client: response body or stream

Admin->>Manager: replace(candidate settings)
Manager->>Manager: publish new generation
Manager->>Manager: retire old generation

Client-->>API: response completes or disconnects
API->>Lease: release()
Lease->>Manager: decrement active leases
Manager->>Runtime: cleanup retired generation when drained
```

</a>
</details>

<sub>Reviews (1): Last reviewed commit: ["refactor: establish
single-owner
applica..."](https://github.com/alishahryar1/free-claude-code/commit/92fc06aa733b7acc34ad6ea50de8b6b4ce5cfac1)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43349002)</sub>

<!-- /greptile_comment -->
2026-07-10 10:52:54 -07:00