alishahryar1--free-claude-code
4a0a0360de
## Problem Anthropic Messages and OpenAI Responses wire models lived under the inbound API adapter. Neutral protocol and provider code therefore duck-typed requests, obscuring ownership and weakening dependency boundaries. ## Changes | Before | After | | --- | --- | | The API package owned Anthropic and Responses protocol models. | Each protocol package owns and publicly exports its wire models. | | Core and provider request paths accepted `Any` and probed known fields with `getattr()`. | Core, transports, and providers consume concrete `MessagesRequest` values. | | Responses conversion and streaming received a dumped request mapping. | Responses conversion and streaming receive one concrete `OpenAIResponsesRequest`. | | Anthropic request snapshots lived in generic tracing code. | Anthropic request snapshots live with the protocol while generic tracing stays protocol-independent. | | Protocol tests and provider request doubles reflected the old API ownership. | Protocol tests live under core and provider tests construct real wire requests. | | The API model package mixed protocol and model-catalog schemas. | The API model package is removed, with catalog schemas beside catalog construction and no compatibility shim. | | Package version was `3.4.18`. | Package version is `3.4.19` with an updated lockfile. | <!-- greptile_comment --> <details open><summary><h3>Greptile Summary</h3></summary> This PR moves protocol request models to their protocol-owned packages. The main changes are: - Anthropic Messages models now live under `core.anthropic`. - OpenAI Responses models now live under `core.openai_responses`. - API handlers, routes, providers, and tests now use concrete protocol request types. - Anthropic request snapshots moved beside the Anthropic protocol models. - API model catalog schemas were kept with catalog response construction. - The package version and lockfile were updated. </details> <h3>Confidence Score: 5/5</h3> This looks safe to merge. No blocking issues were found in the changed code. Internal callers were updated to pass the new concrete protocol models, and no stale internal imports from the removed API model package were identified. 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** - The Pytest run for protocol ownership focused tests completed, showing 71 passed in 6.24s and EXIT\_CODE: 0. - A protocol import smoke script was generated for the import/conversion/trace workflow. - The protocol import smoke run completed successfully, including model ownership output, adapter payload evidence, and trace snapshot evidence, with EXIT\_CODE: 0. <a href="https://app.greptile.com/trex/runs/14066157/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/core/anthropic/models.py | Anthropic wire request and response models moved under the Anthropic protocol package. | | src/free_claude_code/core/anthropic/native_messages_request.py | Native Anthropic serialization now expects concrete `MessagesRequest` instances. | | src/free_claude_code/core/anthropic/conversion.py | OpenAI chat conversion now reads fields directly from `MessagesRequest`. | | src/free_claude_code/core/anthropic/request_snapshot.py | Anthropic request snapshotting moved from generic tracing into the protocol package. | | src/free_claude_code/core/openai_responses/models.py | OpenAI Responses ingress models moved under the Responses protocol package. | | src/free_claude_code/core/openai_responses/input.py | Responses conversion now consumes the concrete request model instead of a dumped mapping. | | src/free_claude_code/core/openai_responses/streaming/assembler.py | Responses stream assembly now reads request attributes from `OpenAIResponsesRequest`. | | src/free_claude_code/api/routes.py | Routes now import protocol request models from their new core owners. | | src/free_claude_code/api/model_catalog.py | Model-list response schemas now live with model catalog construction. | </details> <details open><summary><h3>Flowchart</h3></summary> <a href="#gh-light-mode-only"> ```mermaid %%{init: {'theme': 'neutral'}}%% flowchart LR API[API routes and handlers] --> Anthropic[core.anthropic models and helpers] API --> Responses[core.openai_responses models and adapter] Responses --> Anthropic Providers[Provider clients and transports] --> Anthropic Anthropic --> Trace[core.trace sanitization] API --> Catalog[api.model_catalog response schemas] ``` </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 routes and handlers] --> Anthropic[core.anthropic models and helpers] API --> Responses[core.openai_responses models and adapter] Responses --> Anthropic Providers[Provider clients and transports] --> Anthropic Anthropic --> Trace[core.trace sanitization] API --> Catalog[api.model_catalog response schemas] ``` </a> </details> <sub>Reviews (1): Last reviewed commit: ["Move protocol models to their protocol o..."](https://github.com/alishahryar1/free-claude-code/commit/f1be5c1af4a10da710f80b5f9e7f6044a601f6af) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=43459428)</sub> <!-- /greptile_comment -->
236 行
8.3 KiB
Python
236 行
8.3 KiB
Python
"""Tests for api/optimization_handlers.py."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from free_claude_code.api.optimization_handlers import (
|
|
try_filepath_mock,
|
|
try_optimizations,
|
|
try_prefix_detection,
|
|
try_quota_mock,
|
|
try_suggestion_skip,
|
|
try_title_skip,
|
|
)
|
|
from free_claude_code.config.settings import Settings
|
|
from free_claude_code.core.anthropic.models import (
|
|
ContentBlockText,
|
|
Message,
|
|
MessagesRequest,
|
|
)
|
|
|
|
|
|
def _make_request(
|
|
messages_content: str, max_tokens: int | None = None
|
|
) -> MessagesRequest:
|
|
"""Create a MessagesRequest with a single user message."""
|
|
return MessagesRequest(
|
|
model="claude-3-sonnet",
|
|
max_tokens=max_tokens if max_tokens is not None else 100,
|
|
messages=[Message(role="user", content=messages_content)],
|
|
)
|
|
|
|
|
|
class TestTryPrefixDetection:
|
|
def test_disabled_returns_none(self):
|
|
settings = Settings()
|
|
settings.fast_prefix_detection = False
|
|
req = _make_request("x")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_prefix_detection_request",
|
|
return_value=(True, "/ask"),
|
|
):
|
|
assert try_prefix_detection(req, settings) is None
|
|
|
|
def test_enabled_and_match_returns_response(self):
|
|
settings = Settings()
|
|
settings.fast_prefix_detection = True
|
|
req = _make_request("x")
|
|
with (
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.is_prefix_detection_request",
|
|
return_value=(True, "/ask"),
|
|
),
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.extract_command_prefix",
|
|
return_value="/ask",
|
|
),
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.logger.info"
|
|
) as mock_log_info,
|
|
):
|
|
result = try_prefix_detection(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert block.text == "/ask"
|
|
mock_log_info.assert_called_once_with(
|
|
"Optimization: Fast prefix detection request"
|
|
)
|
|
|
|
def test_enabled_but_no_match_returns_none(self):
|
|
settings = Settings()
|
|
settings.fast_prefix_detection = True
|
|
req = _make_request("x")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_prefix_detection_request",
|
|
return_value=(False, ""),
|
|
):
|
|
assert try_prefix_detection(req, settings) is None
|
|
|
|
|
|
class TestTryQuotaMock:
|
|
def test_disabled_returns_none(self):
|
|
settings = Settings()
|
|
settings.enable_network_probe_mock = False
|
|
req = _make_request("quota", max_tokens=1)
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_quota_check_request",
|
|
return_value=True,
|
|
):
|
|
assert try_quota_mock(req, settings) is None
|
|
|
|
def test_enabled_and_match_returns_response(self):
|
|
settings = Settings()
|
|
settings.enable_network_probe_mock = True
|
|
req = _make_request("quota", max_tokens=1)
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_quota_check_request",
|
|
return_value=True,
|
|
):
|
|
result = try_quota_mock(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert "Quota check passed" in block.text
|
|
|
|
|
|
class TestTryTitleSkip:
|
|
def test_disabled_returns_none(self):
|
|
settings = Settings()
|
|
settings.enable_title_generation_skip = False
|
|
req = _make_request("write a 5-10 word title")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_title_generation_request",
|
|
return_value=True,
|
|
):
|
|
assert try_title_skip(req, settings) is None
|
|
|
|
def test_enabled_and_match_returns_response(self):
|
|
settings = Settings()
|
|
settings.enable_title_generation_skip = True
|
|
req = _make_request("x")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_title_generation_request",
|
|
return_value=True,
|
|
):
|
|
result = try_title_skip(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert block.text == "Conversation"
|
|
|
|
|
|
class TestTrySuggestionSkip:
|
|
def test_disabled_returns_none(self):
|
|
settings = Settings()
|
|
settings.enable_suggestion_mode_skip = False
|
|
req = _make_request("[SUGGESTION MODE: x]")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_suggestion_mode_request",
|
|
return_value=True,
|
|
):
|
|
assert try_suggestion_skip(req, settings) is None
|
|
|
|
def test_enabled_and_match_returns_response(self):
|
|
settings = Settings()
|
|
settings.enable_suggestion_mode_skip = True
|
|
req = _make_request("x")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_suggestion_mode_request",
|
|
return_value=True,
|
|
):
|
|
result = try_suggestion_skip(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert block.text == ""
|
|
|
|
|
|
class TestTryFilepathMock:
|
|
def test_disabled_returns_none(self):
|
|
settings = Settings()
|
|
settings.enable_filepath_extraction_mock = False
|
|
req = _make_request("Command:\nls\nOutput:\nfilepaths")
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_filepath_extraction_request",
|
|
return_value=(True, "ls", "out"),
|
|
):
|
|
assert try_filepath_mock(req, settings) is None
|
|
|
|
def test_enabled_and_match_returns_response(self):
|
|
settings = Settings()
|
|
settings.enable_filepath_extraction_mock = True
|
|
req = _make_request("x")
|
|
with (
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.is_filepath_extraction_request",
|
|
return_value=(True, "ls", "a.txt b.txt"),
|
|
),
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.extract_filepaths_from_command",
|
|
return_value="a.txt\nb.txt",
|
|
),
|
|
):
|
|
result = try_filepath_mock(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert block.text == "a.txt\nb.txt"
|
|
|
|
def test_extract_filepaths_empty_list_still_returns_response(self):
|
|
settings = Settings()
|
|
settings.enable_filepath_extraction_mock = True
|
|
req = _make_request("x")
|
|
with (
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.is_filepath_extraction_request",
|
|
return_value=(True, "ls", "out"),
|
|
),
|
|
patch(
|
|
"free_claude_code.api.optimization_handlers.extract_filepaths_from_command",
|
|
return_value="",
|
|
),
|
|
):
|
|
result = try_filepath_mock(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert block.text == ""
|
|
|
|
|
|
class TestTryOptimizations:
|
|
def test_first_match_wins(self):
|
|
"""Quota mock is first in OPTIMIZATION_HANDLERS; it should win over prefix."""
|
|
settings = Settings()
|
|
settings.enable_network_probe_mock = True
|
|
settings.fast_prefix_detection = True
|
|
req = _make_request("quota", max_tokens=1)
|
|
with patch(
|
|
"free_claude_code.api.optimization_handlers.is_quota_check_request",
|
|
return_value=True,
|
|
):
|
|
result = try_optimizations(req, settings)
|
|
assert result is not None
|
|
block = result.content[0]
|
|
assert isinstance(block, ContentBlockText)
|
|
assert "Quota check passed" in block.text
|
|
|
|
def test_no_match_returns_none(self):
|
|
settings = Settings()
|
|
settings.fast_prefix_detection = False
|
|
settings.enable_network_probe_mock = False
|
|
settings.enable_title_generation_skip = False
|
|
settings.enable_suggestion_mode_skip = False
|
|
settings.enable_filepath_extraction_mock = False
|
|
req = _make_request("random user message")
|
|
assert try_optimizations(req, settings) is None
|