feat(onboarding): verify Test connection without a model id (#792) - #1529
feat(onboarding): verify Test connection without a model id (#792)#1529lifelmy wants to merge 34 commits into
Conversation
Test connection required a model id: probe_llm_provider raises when the model is empty and the Web UI disabled the button until a model was entered. That is a chicken-and-egg bind — a user cannot list a provider's models until the connection is verified, but cannot verify it without already knowing a model. onboarding.provider.probe now falls back to the model-list endpoint (discover_provider_models) when the model field is empty, mapping the listing outcome onto the frozen probe envelope (chat-only timings stay at their never-reached sentinels). The Web UI no longer treats an empty model as a missing required field for draft primary-provider probes; stored/draft profile probes still resolve a concrete model. Fixes TokenRhythm#792
# Conflicts: # src/opensquilla/gateway/rpc_onboarding.py
|
Current follow-up on head Verification:
The remaining failed GitHub check is unrelated to this PR's onboarding diff: Windows high-risk (core) completed 6,947 tests successfully and then A maintainer/admin rerun of failed jobs for run 33724721382 is still needed to clear the transient Windows gate. |
|
Refreshed this PR onto the latest Verification on the refreshed head:
The push superseded the prior CI run containing the unrelated Windows core timeout. GitHub reports the refreshed branch as mergeable; its new CI jobs are running. |
|
Refreshed this PR onto the latest upstream/main (4535029); new head is 33de521. The branch was 1 commit behind and merged cleanly without manual conflict edits. Verification: backend onboarding/RPC/issue-link suites passed (1073 passed, 4 skipped); WebUI privacy suite passed (175 passed); focused ruff and git diff --check passed. |
|
Task A CI follow-up (2026-09-05): the completed Windows recovery-migration shard had one failure in |
# Conflicts: # src/opensquilla/gateway/rpc_onboarding.py
|
Task A refresh (2026-09-05): fetched latest Verification on the refreshed head:
No new comments/reviews or unresolved review threads. |
|
Refreshed this branch onto latest upstream/main (1663b1a); new head is e3c1716. The one-commit base update merged without conflicts, preserving the onboarding Test connection behavior. Verification: backend onboarding suite 72 passed; WebUI privacy Vitest suite 175 passed; focused Ruff and git diff --check passed. |
|
Task A refresh: merged latest |
|
Task A refresh: merged latest |
|
Refreshed onto current upstream/main (cfa8e4b); merge completed without conflicts and preserved the onboarding change. New head: b871d7e. Verification: .venv/bin/pytest -q tests/test_gateway/test_rpc_onboarding.py (72 passed); npm --prefix opensquilla-webui run test:unit -- src/composables/setup/useSetupCatalog.privacy.test.ts (175 passed); git diff --check refs/task-a/upstream/main...HEAD (clean). Fresh CI is running. |
|
CI follow-up: the Ubuntu Desktop recovery profiles job failed in an unrelated profile-consolidation Electron shutdown timeout after its onboarding flow passed. This PR does not touch desktop/electron recovery or shutdown files, and the same CI workflow passed on the freshly merged base cfa8e4b (run 34069076410). I requested a rerun of the failed job; remaining checks are still in progress. |
|
Task A refresh: merged latest |
|
Task A refresh: merged latest Verification: |
|
Task A refresh: merged latest |
|
Task A refresh: PR had gone BEHIND after |
|
Task A refresh: merged latest |
|
Task A refresh: merged latest upstream/main (021063b) without conflicts; new head is 115153d (0 behind). The two upstream commits remove retired runtime steering and isolate router cards, and do not overlap this PR’s model-id-free onboarding connection flow. Verification: .venv/bin/pytest -q tests/test_gateway/test_rpc_onboarding.py (72 passed); focused Ruff and git diff --check passed; WebUI privacy Vitest passed (175 passed). The first push hit a transient GitHub commit_refs error; a retry succeeded and the remote SHA was rechecked. |
|
Task A refresh: fetched and merged latest upstream/main (9486415) without conflicts; new head is 057786a (0 behind / 24 ahead). The upstream change overlaps onboarding RPC/tests but merged cleanly; this PR’s model-id-free Test connection behavior remains covered. Verification: .venv/bin/pytest -q tests/test_gateway/test_rpc_onboarding.py (72 passed); focused Ruff and git diff --check passed; from opensquilla-webui, npm run test:unit -- src/composables/setup/useSetupCatalog.privacy.test.ts (175 passed). Note: an initial root-level direct vitest invocation failed before test collection because it bypassed the WebUI project alias config; rerunning via the package script passed. |
|
Task A refresh: fetched and merged current upstream/main ( Verification:
|
|
Task A refresh: merged current upstream/main ( |
|
Task A refresh: merged current upstream/main (ff4cf2a) without conflicts and pushed 8fd74d1; branch is now 0 behind. The upstream Windows-updater/runtime-diagnostics changes do not overlap the model-id-free onboarding connection path. Verification: .venv/bin/pytest -q tests/test_gateway/test_rpc_onboarding.py (72 passed); focused Ruff passed; npm --prefix opensquilla-webui run test:unit -- src/composables/setup/useSetupCatalog.privacy.test.ts (175 passed); git diff --check passed. |
|
Task A refresh: merged latest upstream/main ( |
|
Task A CI follow-up on current head
|
|
Task A refresh: merged latest |
Problem
Fixes #792.
In Settings → Model services, the Test connection button calls the
onboarding.provider.probeRPC, whose underlyingprobe_llm_provider()hard-requires a model id (an empty model raisesValueError). That is a chicken-and-egg bind for a new user: you cannot know which models a provider serves until you can list them, but the model list is only fetched after the connection verifies — and the connection cannot be verified without already supplying a valid model id.Change
When the model field is empty, connectivity is now verified through the model-list endpoint (
GET /v1/modelsvia the existingdiscover_provider_models()) instead of a one-token chat completion — exactly the behavior the issue proposes.gateway/rpc_onboarding.py):onboarding.provider.probebranches on the model field. With a model it runs the usual usage-accounted chat probe; with an empty model it calls a new_connectivity_probe_via_discovery()helper that runsdiscover_provider_models()and maps the listing outcome (ok/failureKind/detail) back onto the frozen probe envelope. The chat-only timing fields (latencyMs,firstResponseMs,totalMs) stay at their never-reached-the-network sentinels since no chat round-trip happened. Anoklisting that simply returns nothing still counts as reachable (the credentials verified). The wire contract is unchanged.useSetupCatalog.ts): for a draft primary-provider config, an empty model is no longer treated as a missing required field, so the Test connection button is enabled and probes with nomodelparam. Stored/draft profile probes go throughonboarding.llmProfile[.draft].probe, which still resolves a concrete deployment model, so those keep requiring a model.Testing (all run locally, real execution)
uv run ruff check src tests→ all checks passeduv run pytest -q tests/test_gateway/test_rpc_onboarding.py tests/test_contracts tests/test_onboarding→ 1196 passed, 4 skippedtest_provider_probe_without_model_verifies_via_model_list,test_provider_probe_without_model_reports_auth_failureuv run pytest -q tests/test_cli/test_models_probe.py→ 12 passed;uv run pytest -q tests/test_onboarding/test_provider_probe.py→ passeduv run mypy src/opensquilla/gateway/rpc_onboarding.py→ no issuesnpx vitest runonuseSetupCatalog.privacy.test.ts+useSetupProviderForm.test.ts(232 passed) andSetupProviderPanel.test.ts+SetupProviderCredentialCard.test.ts(123 passed); updated the privacy suite's model-less probe expectationsnpm run build(Web UI artifact verified + staged),npm run typecheck(all guards passed),uv build --wheel→ builtopensquilla-0.5.4-py3-none-any.whlNotes
I use a coding assistant to help implement, and I review and take responsibility for the final change. Opened as a draft for maintainer review; happy to adjust the surface (e.g. keep the button enabled but relabel it, or gate the fallback behind a capability flag) if you prefer a different shape.