Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions e2e/test_admin_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def test_unverified_warning_survives_apply(
):
availability: list[Route] = []
page.route(
"**/admin/api/providers/local-status", lambda route: availability.append(route)
"**/admin/api/providers/lmstudio/local-status",
lambda route: availability.append(route),
)
page.route(
"**/admin/api/config/apply",
Expand Down Expand Up @@ -148,16 +149,17 @@ def test_unverified_warning_survives_apply(
expect(page.locator("#messageArea")).to_contain_text("Verification unavailable.")

current = page.locator('[data-provider-check-result="lmstudio"]')
with page.expect_response("**/admin/api/providers/local-status") as response:
with page.expect_response(
"**/admin/api/providers/lmstudio/local-status"
) as response:
old = availability.pop(0)
if restart:
old.fulfill(status=503, json={"detail": "Old check failed"})
else:
payload = old.fetch().json()
for provider in payload["providers"]:
provider.update(
status="offline", label="Offline", message="Old availability result"
)
payload.update(
status="offline", label="Offline", message="Old availability result"
)
old.fulfill(json=payload)
response.value.finished()
page.evaluate("() => new Promise(requestAnimationFrame)")
Expand Down
49 changes: 31 additions & 18 deletions e2e/test_admin_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ def test_local_model_discovery_takes_precedence_over_reachability(
availability: list[Route] = []
page.route("**/admin/api/status", lambda route: startup.append(route))
page.route(
"**/admin/api/providers/local-status", lambda route: availability.append(route)
"**/admin/api/providers/lmstudio/local-status",
lambda route: availability.append(route),
)
_open_admin(page, admin_base_url, {"width": 1280, "height": 720})
page.wait_for_function("!!state.startupRequest && !!state.localStatusRequest")
Expand Down Expand Up @@ -298,22 +299,27 @@ def test_admin_loading_finishes_before_local_availability_checks(
page: Page, admin_base_url: str
) -> None:
pending: list[Route] = []
page.route(
"**/admin/api/providers/local-status", lambda route: pending.append(route)
)

def hold(route):
pending.append(route)
page.evaluate("window.localChecks = (window.localChecks || 0) + 1")

page.route("**/admin/api/providers/*/local-status", hold)
_open_admin(page, admin_base_url, {"width": 1280, "height": 720})
open_provider(page, "nvidia_nim")
key = page.locator("#field-NVIDIA_NIM_API_KEY")
key.fill("unsaved-key")
expect(page.locator("#dirtyState")).to_have_text("No changes")
expect(page.locator("#saveProvider")).to_be_enabled()

route = pending.pop()
payload = route.fetch().json()
providers = {provider["provider_id"]: provider for provider in payload["providers"]}
providers["llamacpp"].update(status="offline", label="Offline", status_code=503)
providers["ollama"].update(status="missing_url", label="Missing URL", base_url="")
route.fulfill(json=payload)
page.wait_for_function("window.localChecks === 3")
for route in pending:
payload = route.fetch().json()
if payload["provider_id"] == "llamacpp":
payload.update(status="offline", label="Offline", status_code=503)
elif payload["provider_id"] == "ollama":
payload.update(status="missing_url", label="Missing URL", base_url="")
route.fulfill(json=payload)
expect(page.locator('[data-provider-check-result="lmstudio"]')).to_have_text(
"Reachable: http://localhost:1234/v1"
)
Expand All @@ -339,9 +345,10 @@ def test_local_availability_failure_does_not_fail_admin_loading(
errors: list[str] = []
page.on("pageerror", lambda error: errors.append(str(error)))
page.route(
"**/admin/api/providers/local-status", lambda route: pending.append(route)
"**/admin/api/providers/lmstudio/local-status",
lambda route: pending.append(route),
)
with page.expect_request("**/admin/api/providers/local-status"):
with page.expect_request("**/admin/api/providers/lmstudio/local-status"):
page.goto(f"{admin_base_url}/admin")
open_provider(page, "nvidia_nim")
expect(page.locator("#field-NVIDIA_NIM_API_KEY")).to_be_editable()
Expand All @@ -353,9 +360,12 @@ def test_local_availability_failure_does_not_fail_admin_loading(

for provider_id in ("lmstudio", "llamacpp", "ollama"):
card = page.locator(f'[data-provider="{provider_id}"]')
expect(card.locator(".provider-check-result")).to_have_text(
"Availability check failed. Use Test to retry."
)
if provider_id == "lmstudio":
expect(card.locator(".provider-check-result")).to_have_text(
"Availability check failed. Use Test to retry."
)
else:
expect(card.locator(".provider-check-result")).to_contain_text("Reachable:")
expect(card.get_by_role("button", name="Edit", exact=True)).to_have_class(
"secondary-button"
)
Expand All @@ -377,7 +387,8 @@ def test_manual_provider_test_takes_precedence_over_automatic_availability(
availability: list[Route] = []
manual: list[Route] = []
page.route(
"**/admin/api/providers/local-status", lambda route: availability.append(route)
"**/admin/api/providers/lmstudio/local-status",
lambda route: availability.append(route),
)
page.route(
"**/admin/api/providers/lmstudio/test", lambda route: manual.append(route)
Expand All @@ -401,7 +412,9 @@ def test_manual_provider_test_takes_precedence_over_automatic_availability(
"Unavailable: Could not refresh this provider's models."
)

with page.expect_response("**/admin/api/providers/local-status") as response:
with page.expect_response(
"**/admin/api/providers/lmstudio/local-status"
) as response:
if manual_finished:
availability.pop().fulfill(status=503, json={"detail": "Check failed"})
else:
Expand All @@ -413,7 +426,7 @@ def test_manual_provider_test_takes_precedence_over_automatic_availability(
expect(result).to_have_text(
"Unavailable: Could not refresh this provider's models."
)
expect(other).to_have_text("Availability check failed. Use Test to retry.")
expect(other).to_have_text("Reachable: http://localhost:11434")
else:
expect(result).to_have_text("Checking...")
expect(other).to_have_text("Reachable: http://localhost:11434")
Expand Down
139 changes: 139 additions & 0 deletions e2e/test_admin_status_independence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import pytest
from playwright.sync_api import expect


@pytest.mark.parametrize(
"held_id,held_button",
[
("claude-vscode", "openClaudeIntegration"),
("vscode-chat", "openVSCodeChatIntegration"),
("codex", "openCodexIntegration"),
("claude-desktop", "openClaudeDesktopIntegration"),
("jetbrains-acp", "openJetBrainsIntegration"),
],
)
def test_each_integration_finishes_without_slowest_card(
page, admin_base_url, held_id, held_button
):
pending = []

def hold(route):
pending.append(route)
page.evaluate("window.heldChecks = (window.heldChecks || 0) + 1")

page.route(f"**/admin/api/integrations/{held_id}", hold)
page.goto(f"{admin_base_url}/admin/integrations")
for visit in range(2):
if visit:
page.get_by_role("button", name="Providers", exact=True).click()
page.get_by_role("button", name="Integrations", exact=True).click()
page.wait_for_function("count => window.heldChecks === count", arg=visit + 1)
expect(page.locator(f"#{held_button}")).to_have_text("Loading…")
for button in [
"openClaudeIntegration",
"openVSCodeChatIntegration",
"openCodexIntegration",
"openClaudeDesktopIntegration",
"openJetBrainsIntegration",
]:
if button != held_button:
expect(page.locator(f"#{button}")).to_have_text("Connect")
expect(page.locator(f"#{button}")).to_be_enabled()
pending.pop().fulfill(status=503, json={"detail": "This check failed"})
expect(page.locator(f"#{held_button}")).to_have_text("Retry")
expect(page.locator(f"#{held_button}")).to_be_enabled()


def test_local_cards_render_before_slowest_check(page, admin_base_url):
pending = []

def hold(route):
pending.append(route)
page.evaluate("window.heldLocalCheck = true")

page.route(
"**/admin/api/providers/ollama/local-status",
hold,
)
page.route(
"**/admin/api/providers/lmstudio/local-status",
lambda route: route.fulfill(
json={
"provider_id": "lmstudio",
"status": "reachable",
"base_url": "http://localhost:1234/v1",
}
),
)
page.route(
"**/admin/api/providers/llamacpp/local-status",
lambda route: route.fulfill(status=503, json={"detail": "Check failed"}),
)
with (
page.expect_response(
"**/admin/api/providers/lmstudio/local-status"
) as lmstudio_response,
page.expect_response(
"**/admin/api/providers/llamacpp/local-status"
) as llamacpp_response,
):
page.goto(f"{admin_base_url}/admin")
page.wait_for_function("window.heldLocalCheck === true")
lmstudio_response.value.finished()
llamacpp_response.value.finished()
expect(page.locator('[data-provider-check-result="lmstudio"]')).to_have_text(
"Reachable: http://localhost:1234/v1"
)
expect(page.locator('[data-provider-check-result="llamacpp"]')).to_have_text(
"Availability check failed. Use Test to retry."
)
assert len(pending) == 1
expect(page.locator('[data-provider-check-result="ollama"]')).to_be_hidden()
pending.pop().fulfill(
json={
"provider_id": "ollama",
"status": "reachable",
"base_url": "http://localhost:11434",
}
)
expect(page.locator('[data-provider-check-result="ollama"]')).to_have_text(
"Reachable: http://localhost:11434"
)


def test_integration_check_from_previous_config_cannot_overwrite_new_result(
page, admin_base_url
):
pending = []

def hold(route):
pending.append(route)
page.evaluate("window.statusChecks = (window.statusChecks || 0) + 1")

page.route("**/admin/api/integrations/vscode-chat", hold)
page.goto(f"{admin_base_url}/admin/integrations")
page.wait_for_function("window.statusChecks === 1")
expect(page.locator("#openJetBrainsIntegration")).to_be_enabled()
page.evaluate("void load()")
page.wait_for_function("window.statusChecks === 2", timeout=2000)
old, current = pending
current.fulfill(
json={
"connected": True,
"paths": None,
"update": {"state": "ready", "changed": False, "message": None},
}
)
expect(page.locator("#openVSCodeChatIntegration")).to_have_text("Disconnect")
with page.expect_response("**/admin/api/integrations/vscode-chat") as response:
old.fulfill(
json={
"connected": False,
"paths": None,
"update": {"state": "ready", "changed": False, "message": None},
}
)
response.value.finished()
page.evaluate("() => new Promise(requestAnimationFrame)")
expect(page.locator("#openVSCodeChatIntegration")).to_have_text("Disconnect")
expect(page.locator("#openVSCodeChatIntegration")).to_be_enabled()
21 changes: 8 additions & 13 deletions src/free_claude_code/api/admin_routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Local admin UI routes and APIs."""

import asyncio
from collections.abc import Awaitable, Callable, Mapping
from pathlib import Path

Expand Down Expand Up @@ -161,26 +160,22 @@ async def admin_status(
return await services.admin.admin_status()


@router.get("/admin/api/providers/local-status")
@router.get("/admin/api/providers/{provider_id}/local-status")
async def local_provider_status(
request: Request, services: ApiServices = Depends(get_services)
provider_id: str, request: Request, services: ApiServices = Depends(get_services)
):
require_loopback_admin(request)
if provider_id not in LOCAL_PROVIDER_PATHS:
raise HTTPException(status_code=404, detail="Local provider not found")
values = {
key: entry.value or ""
for key, entry in (await services.admin.admin_values()).items()
}
checks = await asyncio.gather(
*(
_check_local_provider(
provider_id,
_local_provider_url(provider_id, values),
path,
)
for provider_id, path in LOCAL_PROVIDER_PATHS.items()
)
return await _check_local_provider(
provider_id,
_local_provider_url(provider_id, values),
LOCAL_PROVIDER_PATHS[provider_id],
)
return {"providers": checks}


@router.post("/admin/api/providers/{provider_id}/test")
Expand Down
Loading
Loading