mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-09 16:00:45 +00:00
## Problem
Python 3.14 provides native lazy annotations, but the codebase still
relied on legacy future annotation imports. Those imports also made
type-only import cycles easier to hide instead of fixing ownership
boundaries.
## Changes
| Before | After |
| --- | --- |
| Python files used `from __future__ import annotations`. | Python files
rely on Python 3.14 native lazy annotations. |
| Some runtime modules used `TYPE_CHECKING` or local imports for
required dependencies. | Runtime modules use top-level owner-module
imports with explicit boundaries. |
| Local and GitHub guardrails only rejected type ignore suppressions. |
Local and GitHub guardrails reject type ignore suppressions and legacy
future annotation imports. |
| Agent docs only documented the no-type-ignore rule. | Agent docs
document the Python 3.14 annotation and import-boundary rules. |
<!-- greptile_comment -->
<details open><summary><h3>Greptile Summary</h3></summary>
This PR moves the codebase to Python 3.14 native lazy annotations. The
main changes are:
- Removed legacy `from __future__ import annotations` imports across
Python modules.
- Promoted selected runtime dependencies from `TYPE_CHECKING` or local
imports to explicit owner-module imports.
- Added local, GitHub, and contract-test guardrails to reject legacy
future annotation imports.
- Updated agent docs with the annotation and import-boundary rules.
- Bumped the package patch version for production-file changes.
</details>
<h3>Confidence Score: 5/5</h3>
Safe to merge with low risk.
The changes are mostly mechanical annotation cleanup with matching CI
and contract-test guardrails. Reviewed import-boundary updates did not
show a confirmed runtime cycle or dependency break.
No files require special 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**
- Performed an end-to-end validation of the guardrail contract suite: an
environment check confirmed uv availability, a guardrail pytest run used
CPython 3.14.0 with 5 passing contract tests, 3 focused CI-script tests
passed, and the direct CI suppressions guardrail command (including the
legacy future-annotations grep) also passed.
<a
href="https://app.greptile.com/trex/runs/13303335/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 |
|----------|----------|
| api/runtime.py | Moves messaging, CLI manager, session, limiter, and
tree dependencies from local/type-checking imports to explicit top-level
owner-module imports. |
| messaging/platforms/telegram.py | Removes future annotations and
promotes Telegram SDK type imports into the existing availability guard.
|
| messaging/platforms/telegram_inbound.py | Removes future annotations
and imports Telegram SDK types at module scope for inbound
normalization. |
| tests/contracts/test_import_boundaries.py | Adds an AST contract that
rejects legacy future annotation imports across Python files. |
| scripts/ci.sh | Extends the local suppression check to reject legacy
future annotation imports alongside type-ignore suppressions. |
| scripts/ci.ps1 | Mirrors the local PowerShell CI suppression check for
legacy future annotations. |
| .github/workflows/tests.yml | Renames and broadens the GitHub
guardrail job to reject both type suppressions and legacy future
annotations. |
| pyproject.toml | Bumps the patch version for production-file changes.
|
</details>
<details open><summary><h3>Sequence Diagram</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Dev as Developer/CI
participant Guard as Suppression guard
participant AST as Import-boundary contract test
participant Py as Python modules
Dev->>Guard: Run local/GitHub suppression check
Guard->>Py: "Scan *.py for type ignores and future annotations"
Guard-->>Dev: Fail if legacy annotation import remains
Dev->>AST: Run pytest contract tests
AST->>Py: Parse imports with ast
AST-->>Dev: Assert no future annotations/import-boundary violations
Py-->>Dev: Use Python 3.14 native lazy annotations
```
</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 Dev as Developer/CI
participant Guard as Suppression guard
participant AST as Import-boundary contract test
participant Py as Python modules
Dev->>Guard: Run local/GitHub suppression check
Guard->>Py: "Scan *.py for type ignores and future annotations"
Guard-->>Dev: Fail if legacy annotation import remains
Dev->>AST: Run pytest contract tests
AST->>Py: Parse imports with ast
AST-->>Dev: Assert no future annotations/import-boundary violations
Py-->>Dev: Use Python 3.14 native lazy annotations
```
</a>
</details>
<sub>Reviews (2): Last reviewed commit: ["Remove legacy future
annotations
import"](6e6cda69da)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=41875785)</sub>
<!-- /greptile_comment -->
204 lines
7.6 KiB
Python
204 lines
7.6 KiB
Python
"""Claude Messages API product flow."""
|
|
|
|
from collections.abc import AsyncIterator, Callable
|
|
from dataclasses import dataclass, replace
|
|
|
|
from fastapi.responses import JSONResponse
|
|
from loguru import logger
|
|
|
|
from api.detection import is_safety_classifier_request
|
|
from api.model_router import ModelRouter, RoutedMessagesRequest
|
|
from api.models.anthropic import MessagesRequest
|
|
from api.optimization_handlers import try_optimizations
|
|
from api.provider_execution import ProviderExecutionService, TokenCounter
|
|
from api.request_errors import require_non_empty_messages, unexpected_http_exception
|
|
from api.response_streams import anthropic_sse_streaming_response
|
|
from api.web_tools.egress import WebFetchEgressPolicy, web_fetch_allowed_scheme_set
|
|
from api.web_tools.request import (
|
|
is_web_server_tool_request,
|
|
openai_chat_upstream_server_tool_error,
|
|
)
|
|
from api.web_tools.streaming import stream_web_server_tool_response
|
|
from config.provider_catalog import PROVIDER_CATALOG
|
|
from config.settings import Settings
|
|
from core.anthropic import aggregate_anthropic_sse_to_message, get_token_count
|
|
from core.trace import trace_event
|
|
from providers.base import BaseProvider
|
|
from providers.exceptions import InvalidRequestError, ProviderError
|
|
|
|
_OPENAI_CHAT_UPSTREAM_IDS = frozenset(
|
|
provider_id
|
|
for provider_id, descriptor in PROVIDER_CATALOG.items()
|
|
if descriptor.transport_type == "openai_chat"
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _MessagesStreamResult:
|
|
body: AsyncIterator[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _MessagesCompleteResult:
|
|
response: object
|
|
|
|
|
|
ProviderGetter = Callable[[str], BaseProvider]
|
|
_MessagesResult = _MessagesStreamResult | _MessagesCompleteResult
|
|
MessageIntercept = Callable[[RoutedMessagesRequest], _MessagesResult | None]
|
|
|
|
|
|
class MessagesHandler:
|
|
"""Handle Anthropic-compatible Messages requests."""
|
|
|
|
def __init__(
|
|
self,
|
|
settings: Settings,
|
|
provider_getter: ProviderGetter,
|
|
*,
|
|
model_router: ModelRouter | None = None,
|
|
token_counter: TokenCounter = get_token_count,
|
|
provider_execution: ProviderExecutionService | None = None,
|
|
) -> None:
|
|
self._settings = settings
|
|
self._model_router = model_router or ModelRouter(settings)
|
|
self._token_counter = token_counter
|
|
self._provider_execution = provider_execution or ProviderExecutionService(
|
|
settings,
|
|
provider_getter,
|
|
token_counter=token_counter,
|
|
)
|
|
self._message_intercepts: tuple[MessageIntercept, ...] = (
|
|
self._intercept_web_server_tool,
|
|
self._intercept_local_optimization,
|
|
)
|
|
|
|
async def create(self, request_data: MessagesRequest) -> object:
|
|
"""Create an Anthropic-compatible message response."""
|
|
try:
|
|
require_non_empty_messages(request_data.messages)
|
|
routed = self._model_router.resolve_messages_request(request_data)
|
|
routed = self._apply_message_routing_policies(routed)
|
|
self._reject_unsupported_server_tools(routed)
|
|
|
|
result = self._run_message_intercepts(routed)
|
|
if result is None:
|
|
logger.debug("No optimization matched, routing to provider")
|
|
result = _MessagesStreamResult(
|
|
self._provider_execution.stream(
|
|
routed,
|
|
wire_api="messages",
|
|
raw_log_label="FULL_PAYLOAD",
|
|
raw_log_payload=routed.request.model_dump(),
|
|
)
|
|
)
|
|
return await self._to_public_response(result, stream=request_data.stream)
|
|
except ProviderError:
|
|
raise
|
|
except Exception as exc:
|
|
raise unexpected_http_exception(
|
|
self._settings, exc, context="CREATE_MESSAGE_ERROR"
|
|
) from exc
|
|
|
|
async def _to_public_response(
|
|
self, result: _MessagesResult, *, stream: bool | None
|
|
) -> object:
|
|
if isinstance(result, _MessagesCompleteResult):
|
|
return result.response
|
|
if stream is False:
|
|
# Non-streaming clients (e.g. Claude Code utility calls) need a
|
|
# complete JSON Message; the internal pipeline is always SSE, so
|
|
# serving that raw here breaks the client SDK's response parse.
|
|
message, error = await aggregate_anthropic_sse_to_message(result.body)
|
|
if error is not None and not message.get("content"):
|
|
return JSONResponse(
|
|
status_code=502,
|
|
content={"type": "error", "error": error},
|
|
)
|
|
return JSONResponse(content=message)
|
|
return anthropic_sse_streaming_response(result.body)
|
|
|
|
def _reject_unsupported_server_tools(self, routed: RoutedMessagesRequest) -> None:
|
|
if routed.resolved.provider_id not in _OPENAI_CHAT_UPSTREAM_IDS:
|
|
return
|
|
tool_err = openai_chat_upstream_server_tool_error(
|
|
routed.request,
|
|
web_tools_enabled=self._settings.enable_web_server_tools,
|
|
)
|
|
if tool_err is not None:
|
|
raise InvalidRequestError(tool_err)
|
|
|
|
def _apply_message_routing_policies(
|
|
self, routed: RoutedMessagesRequest
|
|
) -> RoutedMessagesRequest:
|
|
if not is_safety_classifier_request(routed.request):
|
|
return routed
|
|
changed = routed.resolved.thinking_enabled
|
|
trace_event(
|
|
stage="routing",
|
|
event="api.optimization.safety_classifier_no_thinking",
|
|
source="api",
|
|
model=routed.request.model,
|
|
changed=changed,
|
|
)
|
|
if not changed:
|
|
return routed
|
|
return RoutedMessagesRequest(
|
|
request=routed.request,
|
|
resolved=replace(routed.resolved, thinking_enabled=False),
|
|
)
|
|
|
|
def _run_message_intercepts(
|
|
self, routed: RoutedMessagesRequest
|
|
) -> _MessagesResult | None:
|
|
for intercept in self._message_intercepts:
|
|
result = intercept(routed)
|
|
if result is not None:
|
|
return result
|
|
return None
|
|
|
|
def _intercept_web_server_tool(
|
|
self, routed: RoutedMessagesRequest
|
|
) -> _MessagesResult | None:
|
|
if not self._settings.enable_web_server_tools:
|
|
return None
|
|
if not is_web_server_tool_request(routed.request):
|
|
return None
|
|
|
|
input_tokens = self._token_counter(
|
|
routed.request.messages, routed.request.system, routed.request.tools
|
|
)
|
|
trace_event(
|
|
stage="routing",
|
|
event="api.optimization.web_server_tool",
|
|
source="api",
|
|
model=routed.request.model,
|
|
)
|
|
egress = WebFetchEgressPolicy(
|
|
allow_private_network_targets=self._settings.web_fetch_allow_private_networks,
|
|
allowed_schemes=web_fetch_allowed_scheme_set(
|
|
self._settings.web_fetch_allowed_schemes
|
|
),
|
|
)
|
|
return _MessagesStreamResult(
|
|
stream_web_server_tool_response(
|
|
routed.request,
|
|
input_tokens=input_tokens,
|
|
web_fetch_egress=egress,
|
|
verbose_client_errors=self._settings.log_api_error_tracebacks,
|
|
),
|
|
)
|
|
|
|
def _intercept_local_optimization(
|
|
self, routed: RoutedMessagesRequest
|
|
) -> _MessagesResult | None:
|
|
optimized = try_optimizations(routed.request, self._settings)
|
|
if optimized is None:
|
|
return None
|
|
trace_event(
|
|
stage="routing",
|
|
event="api.optimization.short_circuit",
|
|
source="api",
|
|
model=routed.request.model,
|
|
)
|
|
return _MessagesCompleteResult(optimized)
|