mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-10-19 15:33:34 +00:00
Some checks are pending
Post merge / Warm dependency cache (ubuntu-latest) (push) Waiting to run
Post merge / Warm dependency cache (windows-latest) (push) Waiting to run
Post merge / Warm dependency cache (macos-26) (push) Waiting to run
Post merge / Warm dependency cache (macos-26-intel) (push) Waiting to run
Post merge / Warm dependency cache (macos-latest) (push) Waiting to run
Post merge / Publish to PyPI (push) Waiting to run
## Why
Closes #2106. A slow provider held back model lists and integrations
even after other providers were ready. Shared waits also let Code
requests stall Admin progress, unrelated account checks prolong settings
saves, and one integration's file update block another.
## How
- Serve current configured and discovered models immediately. Code waits
only for the selected provider's metadata, including saved models
awaiting rediscovery, with the existing initialization budget and
generation checks.
- Publish catalog changes progressively. Notify integration consumers
independently from Codex file writes, coalesce publication, and track
the exact version written. File waiters follow newer publications
without reporting pending work as a failure. Explicit model refresh
retries an unpublished version even when provider metadata is unchanged.
- Let integrations update their own files concurrently. Preserve
exclusive settings publication and cancellation draining so active
writes cannot outlive their settings generation.
- Give Admin polling, account checks, model suggestions, provider
actions, and Code bootstrap independent completion. Coalesce Code
refreshes and preserve selections, drafts, open searches, and
stale-response protection as models arrive.
- Add regression coverage for held providers and requests, independent
integration writes, publication ordering, cancellation, partial
catalogs, and late model updates. Provider-ordering browser tests answer
subsequent status polls after releasing their initial held request.
Native launchers use the catalog available at launch. Apps that retain
that snapshot may need a reload or relaunch to show later discoveries.
Settings commits still wait for active integration writes to finish
before publishing new settings.
<!-- greptile_comment -->
<!-- greptile_summary -->
<h2><a
href="https://app.greptile.com/api/retrigger?id=82968429"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"><img
alt="Retrigger"
src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"
align="right"></picture></a><a
href="https://app.greptile.com/alishahryar1/github/Alishahryar1/free-claude-code/-/pull/2107"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptileDark.svg?v=2"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"><img
alt="View in Greptile"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2"
align="right"></picture></a>Confidence Score: 5/5</h2>
<!-- greptile-impact-verdict --> No outstanding findings block merging
the reviewed changes.
<details open><summary><h3>Summary</h3></summary>
Startup components can become ready independently:
- Model lists grow as providers become ready, while Code waits only for
its selected provider.
- Integration files update independently, while Codex tracks the catalog
version it writes.
- Admin checks and Code startup can finish without waiting for one
another.
The latest change updates the provider-ordering browser test to answer
subsequent status polls after releasing the initial held request. No new
actionable issues were found.
Both earlier findings remain addressed in the current code: saved models
wait for their provider's pending discovery within the initialization
budget, and explicit model refresh requests publication again when the
current catalog version has not been written.
</details>
<!-- greptile_confidence_score:5 -->
<sub>Reviews (4) · Last reviewed commit: ["Complete subsequent status
polls in
prov..."](2780127722)
· [Reviewed by
Greptile](https://www.greptile.com/?utm_source=greptile_review&utm_medium=github&utm_campaign=review_public&utm_content=alishahryar1/free-claude-code)</sub>
<!-- /greptile_comment -->
265 lines
9.9 KiB
Python
265 lines
9.9 KiB
Python
import json
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from free_claude_code.application.code_sessions.models import CodeValidationError
|
|
from free_claude_code.application.model_metadata import ProviderModelInfo
|
|
from free_claude_code.application.ports import (
|
|
ModelCatalogSnapshot,
|
|
RequestRuntimeLease,
|
|
RequestRuntimePort,
|
|
)
|
|
from free_claude_code.cli.launchers.catalog_http import catalog_models_from_response
|
|
from free_claude_code.config.settings import Settings
|
|
from free_claude_code.core.json_types import JsonObject
|
|
from free_claude_code.core.model_capabilities import ModelInputModality
|
|
from free_claude_code.harnesses.codex_model_catalog import build_codex_model_catalog
|
|
from free_claude_code.runtime.codex_app_server import CodexHarnessFactory
|
|
from free_claude_code.runtime.codex_catalog import (
|
|
CodexModelCatalogPublisher,
|
|
write_codex_model_catalog,
|
|
)
|
|
from tests.harnesses.test_codex_model_catalog import _models_payload
|
|
|
|
|
|
class FakeRequestRuntime(RequestRuntimePort):
|
|
def __init__(
|
|
self,
|
|
*,
|
|
settings: Settings,
|
|
cached_infos: tuple[ProviderModelInfo, ...] = (),
|
|
) -> None:
|
|
self._settings = settings
|
|
self._cached_infos = cached_infos
|
|
|
|
async def acquire(
|
|
self, *, include_model_infos: bool = False
|
|
) -> RequestRuntimeLease:
|
|
del include_model_infos
|
|
raise AssertionError("Catalog publication must not acquire a provider lease.")
|
|
|
|
def catalog_snapshot(self) -> ModelCatalogSnapshot:
|
|
return ModelCatalogSnapshot(self._settings, self._cached_infos)
|
|
|
|
async def wait_for_model_catalog(self, model: str) -> ModelCatalogSnapshot:
|
|
return self.catalog_snapshot()
|
|
|
|
def catalog_status(self) -> JsonObject:
|
|
raise AssertionError("Catalog publication must not query startup status.")
|
|
|
|
def current_settings(self) -> Settings:
|
|
return self._settings
|
|
|
|
def cached_model_info(
|
|
self, provider_id: str, model_id: str
|
|
) -> ProviderModelInfo | None:
|
|
del provider_id, model_id
|
|
return None
|
|
|
|
def cached_prefixed_model_infos(self) -> tuple[ProviderModelInfo, ...]:
|
|
return self._cached_infos
|
|
|
|
|
|
def _runtime() -> FakeRequestRuntime:
|
|
settings = Settings().model_copy(update={"model": "nvidia_nim/configured"})
|
|
return FakeRequestRuntime(
|
|
settings=settings,
|
|
cached_infos=(
|
|
ProviderModelInfo("open_router/discovered", context_window_tokens=100_000),
|
|
),
|
|
)
|
|
|
|
|
|
def _catalog_slugs(path: Path) -> list[str]:
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
return [model["slug"] for model in payload["models"]]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_known_vision_survives_publication_and_browser_preparation(
|
|
tmp_path: Path,
|
|
):
|
|
runtime = FakeRequestRuntime(
|
|
settings=Settings().model_copy(update={"model": "nvidia_nim/configured"}),
|
|
cached_infos=(
|
|
ProviderModelInfo(
|
|
"open_router/vision",
|
|
input_modalities=frozenset(
|
|
{ModelInputModality.TEXT, ModelInputModality.IMAGE}
|
|
),
|
|
context_window_tokens=131072,
|
|
),
|
|
),
|
|
)
|
|
path = tmp_path / "catalog.json"
|
|
CodexModelCatalogPublisher(path).publish(runtime)
|
|
entries = {row["slug"]: row for row in json.loads(path.read_text())["models"]}
|
|
assert entries["open_router/vision"]["input_modalities"] == ["text", "image"]
|
|
selected = await CodexHarnessFactory(runtime, binary="codex").prepare(
|
|
"open_router/vision", None, "config"
|
|
)
|
|
prepared = json.loads(json.dumps(build_codex_model_catalog(selected.models)))
|
|
assert next(
|
|
row for row in prepared["models"] if row["slug"] == "open_router/vision"
|
|
)["input_modalities"] == ["text", "image"]
|
|
|
|
|
|
def test_publisher_projects_the_application_catalog_without_compatibility_ids(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
catalog_path = tmp_path / "codex-model-catalog.json"
|
|
publisher = CodexModelCatalogPublisher(catalog_path)
|
|
|
|
publisher.publish(_runtime())
|
|
|
|
assert _catalog_slugs(catalog_path) == [
|
|
"nvidia_nim/configured",
|
|
"open_router/discovered",
|
|
]
|
|
|
|
|
|
def test_empty_projection_preserves_existing_catalog(tmp_path: Path) -> None:
|
|
catalog_path = tmp_path / "codex-model-catalog.json"
|
|
catalog_path.write_text("last known good\n", encoding="utf-8")
|
|
publisher = CodexModelCatalogPublisher(catalog_path)
|
|
|
|
with (
|
|
patch(
|
|
"free_claude_code.runtime.codex_catalog.build_codex_model_catalog",
|
|
return_value={"models": []},
|
|
),
|
|
pytest.raises(ValueError, match="no routable models"),
|
|
):
|
|
publisher.publish(_runtime())
|
|
|
|
assert catalog_path.read_text(encoding="utf-8") == "last known good\n"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_code_picker_and_native_selection_use_the_same_advertised_efforts():
|
|
factory = CodexHarnessFactory(_runtime(), binary="codex")
|
|
advertised = factory.catalog()
|
|
assert advertised.default_model == "nvidia_nim/configured"
|
|
model = advertised.models[1]
|
|
assert advertised.models[0].context_window_tokens is None
|
|
assert model.context_window_tokens == 100_000
|
|
assert model.reasoning_efforts == ("off", "low", "medium", "high", "xhigh", "max")
|
|
selected = await factory.prepare(model.id, None, "config")
|
|
assert selected.model == model.id
|
|
assert selected.reasoning_effort == model.default_reasoning_effort == "medium"
|
|
for effort in model.reasoning_efforts:
|
|
assert (
|
|
await factory.prepare(model.id, effort, "config")
|
|
).reasoning_effort == effort
|
|
with pytest.raises(CodeValidationError, match="effort"):
|
|
(await factory.prepare(model.id, "unsupported", "config"))
|
|
with pytest.raises(CodeValidationError, match="model"):
|
|
(await factory.prepare("missing/model", None, "config"))
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_non_reasoning_model_off_selection_is_available():
|
|
runtime = FakeRequestRuntime(
|
|
settings=Settings().model_copy(update={"model": "nvidia_nim/configured"}),
|
|
cached_infos=(
|
|
ProviderModelInfo("open_router/text-only", supports_thinking=False),
|
|
),
|
|
)
|
|
factory = CodexHarnessFactory(runtime, binary="codex")
|
|
model = next(
|
|
entry
|
|
for entry in factory.catalog().models
|
|
if entry.id == "open_router/text-only"
|
|
)
|
|
assert model.reasoning_efforts == ("off",)
|
|
assert model.default_reasoning_effort == "off"
|
|
assert (await factory.prepare(model.id, "off", "config")).reasoning_effort == "off"
|
|
|
|
|
|
def test_catalog_writer_skips_identical_content_and_replaces_changes(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
catalog_path = tmp_path / "codex-model-catalog.json"
|
|
first = build_codex_model_catalog(
|
|
catalog_models_from_response(_models_payload("nvidia_nim/first"))
|
|
)
|
|
second = build_codex_model_catalog(
|
|
catalog_models_from_response(_models_payload("nvidia_nim/second"))
|
|
)
|
|
|
|
assert write_codex_model_catalog(catalog_path, first) is True
|
|
assert write_codex_model_catalog(catalog_path, first) is False
|
|
assert list(tmp_path.glob(".codex-model-catalog.json.*.tmp")) == []
|
|
|
|
assert write_codex_model_catalog(catalog_path, second) is True
|
|
assert json.loads(catalog_path.read_text(encoding="utf-8")) == second
|
|
assert list(tmp_path.glob(".codex-model-catalog.json.*.tmp")) == []
|
|
|
|
|
|
def test_catalog_writer_cleans_temporary_file_after_replace_failure(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
catalog_path = tmp_path / "codex-model-catalog.json"
|
|
catalog_path.write_text("previous\n", encoding="utf-8")
|
|
|
|
def fail_replace(_source: Path, _destination: Path) -> Path:
|
|
raise PermissionError("destination is locked")
|
|
|
|
monkeypatch.setattr(Path, "replace", fail_replace)
|
|
|
|
with pytest.raises(PermissionError, match="locked"):
|
|
write_codex_model_catalog(
|
|
catalog_path,
|
|
build_codex_model_catalog(
|
|
catalog_models_from_response(_models_payload("nvidia_nim/replacement"))
|
|
),
|
|
)
|
|
|
|
assert catalog_path.read_text(encoding="utf-8") == "previous\n"
|
|
assert list(tmp_path.glob(".codex-model-catalog.json.*.tmp")) == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_catalog_refresh_preserves_selection_and_behavior_fingerprint():
|
|
runtime = FakeRequestRuntime(
|
|
settings=Settings().model_copy(update={"model": "open_router/Zulu"}),
|
|
cached_infos=(
|
|
ProviderModelInfo("open_router/Zulu", context_window_tokens=32000),
|
|
),
|
|
)
|
|
factory = CodexHarnessFactory(runtime, binary="codex")
|
|
before = await factory.prepare("open_router/Zulu", "high", "config")
|
|
runtime._cached_infos = (
|
|
ProviderModelInfo("deepseek/alpha"),
|
|
*runtime._cached_infos,
|
|
)
|
|
after = await factory.prepare("open_router/Zulu", "high", "config")
|
|
assert after.model == before.model
|
|
assert after.reasoning_effort == before.reasoning_effort
|
|
assert after.configuration_key == before.configuration_key
|
|
assert factory.catalog().default_model == "open_router/Zulu"
|
|
assert factory.catalog().models[0].id == "deepseek/alpha"
|
|
runtime._cached_infos = (
|
|
ProviderModelInfo("open_router/Zulu", context_window_tokens=64000),
|
|
)
|
|
changed = await factory.prepare("open_router/Zulu", "high", "config")
|
|
assert changed.configuration_key != before.configuration_key
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"modalities", [frozenset(), frozenset({ModelInputModality.IMAGE})]
|
|
)
|
|
def test_non_text_capabilities_retain_the_native_text_fallback(modalities):
|
|
from free_claude_code.application.model_catalog import read_model_catalog
|
|
|
|
snapshot = ModelCatalogSnapshot(
|
|
Settings().model_copy(update={"model": "open_router/model"}),
|
|
(ProviderModelInfo("open_router/model", input_modalities=modalities),),
|
|
)
|
|
catalog = read_model_catalog(snapshot)
|
|
assert catalog.models[0].input_modalities == modalities
|
|
payload = json.loads(json.dumps(build_codex_model_catalog(catalog.models)))
|
|
assert payload["models"][0]["input_modalities"] == ["text"]
|