fix(ai-settings): allow free-text Azure deployment names (#5213) - #5222
Conversation
…i#5213) Azure AI Foundry separates the base model id a deployment was created from (`gpt-5.6-terra-2026-07-09`) from the user-chosen deployment name (`gpt-5.6-terra`) that actually routes the request. Its OpenAI-compatible surface keys the request body's `model` field on the deployment name, so a value taken from the provider's `/models` catalog yields "Model not found" for every inference call. Routing was never the problem: the `<model>` half of a `"<slug>:<model>"` provider string already reaches the wire verbatim. The defect was that the AI settings UI sourced that value exclusively from the probed catalog, so a deployment name that was not in the catalog could not be entered at all. - Add `azureDeployment.ts`: endpoint-host detection for Azure resources (`*.openai.azure.com`, `*.services.ai.azure.com`, …) plus a helper that spots a stored value taken verbatim from the catalog. - Both model pickers (global "Use Your Own Models" card and the per-workload routing dialog) gain a manual-entry escape hatch, so an off-catalog model id is reachable for every provider, not just Azure. - Azure connections relabel the field to "Deployment name", default to free text, carry the "this is not the model ID" helper, and no longer auto-select the first catalog entry. - Existing connections are prompted, not rewritten: a stored value that is verbatim a catalog entry shows an inline hint to confirm it is the deployment name. Nothing on disk is migrated behind the user's back. - The provider editor points at the deployment field once an Azure endpoint is entered. Detection is by endpoint host rather than slug because Azure is reachable only through the generic "Add cloud provider" flow today, so the user picks the slug and the host is the one stable signal. Regression tests fail before this change and pass after: the deployment name survives to the persisted routing, the field is not auto-seeded from the catalog, and non-Azure providers keep their dropdown.
|
@coderabbitai review |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 1 minute Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthroughAzure Foundry endpoints now use free-text deployment-name inputs in global and per-workload AI settings. Non-Azure providers retain catalog selection with manual fallback. Provider probe handling, endpoint guidance, detection helpers, tests, localized strings, and coverage documentation were added. ChangesAzure deployment routing
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Settings
participant ModelEntryField
participant ProviderProbe
participant Routing
Settings->>ModelEntryField: classify Azure endpoint
ModelEntryField-->>Settings: render deployment-name input
Settings->>Routing: persist typed deployment name
Settings->>ProviderProbe: verify provider models
ProviderProbe-->>Settings: return success or probe failure
Settings->>ProviderProbe: retry with skipProbe
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51aab102d7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
| Filename | Overview |
|---|---|
| app/src/components/settings/panels/azureDeployment.ts | New module: host-based Azure detection helpers with dot-boundary anchor guards; well-covered by 11 unit tests. |
| app/src/components/settings/panels/ai/ModelEntryField.tsx | New shared model/deployment-name field component; loading guard correctly gates on explicit manualEntry rather than the effective mode. |
| app/src/components/settings/panels/AIPanel.tsx | Core AI settings panel. Probe-skip flow and syncToEndpoint wiring are solid; probeFailed is not cleared on endpoint/key edits, causing stale bypass button. |
| app/src/components/settings/panels/tests/AIPanel.test.tsx | Added 11 Azure-specific UI regression tests covering deployment-name entry, auto-seed suppression, probe-skip flow, legacy-value hint, and endpoint nudge. |
| app/src/components/settings/panels/tests/azureDeployment.test.ts | 11 unit tests for all four helper functions; covers sovereign cloud hosts, dot-boundary lookalike rejection, v1-path variants, and null/undefined inputs. |
| app/src/lib/i18n/en.ts | Adds 9 new i18n keys for Azure deployment-name guidance, probe-failure messaging, and the v1 endpoint nudge; deploymentNamePlaceholder correctly allowlisted. |
Reviews (4): Last reviewed commit: "fix(ai-settings): scope the verification..." | Re-trigger Greptile
|
Closing: this implementation was not requested — the ask was only for a plain-language (ELI5) explanation of the approach, which has been captured. Branch is preserved and this can be reopened if we decide to pursue #5213 later. |
|
Reopening at the human's request — keeping this open. |
…ce (tinyhumansai#5213) The GlobalOwnModelSelector's manual-entry field was missing the `mono` prop its equivalent in CustomRoutingDialog already carries. Deployment names and model identifiers are opaque tokens users compare character by character, so they should render monospace in both pickers, not just one.
…nsai#5213) The coverage gate failed at 64% on changed lines (needs 80%): 28 uncovered lines in AIPanel.tsx, almost all of them the Azure branches this PR adds. Four tests, aimed at the uncovered clusters: - The legacy hint fires when a stored value is verbatim a catalog base model id — the fingerprint of a pre-fix Azure selection — and stays silent when the deployment name is off-catalog. The always-on explainer is asserted alongside it. - The escape hatch works in BOTH directions: Azure opens on free text, can be switched to the catalog, and back to typing. Only the forward direction was exercised before. - The per-workload custom-routing dialog gets the same Azure treatment. It is an independent second model picker (reached via the Advanced routing mode), and it carried the largest block of uncovered lines — without it, per-workload routing would stay stuck on catalog base model ids even with the main selector fixed. Ran locally: 58/58 pass in AIPanel.test.tsx.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
app/src/components/settings/panels/AIPanel.tsx (1)
2001-2010: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo diagnostics on the new Azure-detection state transitions.
manualModelEntryis a new state machine driven entirely byisAzureFoundryEndpoint, but none of its transition points (initial state, provider-switch handler) log anything — unlike the adjacent models-fetch flow in this same file, which already usesconsole.debug/console.error. Given this PR exists specifically to fix an opaque "Model not found" failure, a debug log at the Azure-detection decision points (e.g.console.debug('[ai-settings] azure endpoint detected, defaulting to manual entry', slug)) would materially help future triage.As per coding guidelines, "New or changed flows must include verbose, grep-friendly diagnostics covering entry/exit, branches, external calls, retries/timeouts, state transitions, and errors."
Also applies to: 2253-2265, 2852-2863
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/AIPanel.tsx` around lines 2001 - 2010, Add grep-friendly console.debug diagnostics to the manualModelEntry state flow, including the initial Azure endpoint decision and the provider-switch and other transition handlers around the referenced areas. Log the provider slug and whether isAzureFoundryEndpoint detected Azure before updating state, while preserving the existing state behavior and nearby error logging conventions.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx`:
- Around line 535-558: Extend the per-workload routing test after editing
deploymentInput to complete and save the dialog using the existing confirmation
controls, then assert the saved workload routing contains provider
`cloud:azure-foundry` and model `workload-deployment`. Keep the current field
and explanatory-text assertions, and use the existing rendered UI/state
assertion pattern from AIPanel tests.
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 2065-2079: Extract the duplicated Azure model-entry state and UI
from CustomRoutingDialog and GlobalOwnModelSelector into a shared
useAzureModelEntry hook and ModelDeploymentField component. Move Azure
detection, manual-entry initialization, useManualModelEntry,
showAzureLegacyModelHint, catalog/manual rendering, toggle, hints, and
placeholder behavior into these shared symbols, preserving both components’
required guards and existing user-facing behavior. Replace each duplicated
implementation with the shared hook/component.
In `@app/src/components/settings/panels/azureDeployment.ts`:
- Around line 31-37: Add the Azure Government suffix “openai.azure.us” to the
AZURE_ENDPOINT_HOSTS allowlist so isAzureFoundryEndpoint recognizes
sovereign-cloud Azure OpenAI endpoints while preserving the existing host
entries.
In `@app/src/lib/i18n/id.ts`:
- Around line 4725-4728: Update the settings.ai.deploymentNamePlaceholder
translation to use a deployment-shaped example such as “my-gpt-deployment” or
“prod-chat-eastus” instead of the model-like “gpt-5.6-terra”; keep the adjacent
label and help text unchanged.
In `@app/src/lib/i18n/ko.ts`:
- Line 4663: Update the i18n action-label definitions and AIPanel.tsx selection
logic so Azure providers use a dedicated deployment-name action key, such as
settings.ai.enterDeploymentNameManuallyAction, while non-Azure providers retain
the existing model-ID key. Add the new key to en.ts and every locale file, using
the appropriate translated “enter deployment name manually” label.
---
Nitpick comments:
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 2001-2010: Add grep-friendly console.debug diagnostics to the
manualModelEntry state flow, including the initial Azure endpoint decision and
the provider-switch and other transition handlers around the referenced areas.
Log the provider slug and whether isAzureFoundryEndpoint detected Azure before
updating state, while preserving the existing state behavior and nearby error
logging conventions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4cd39eae-4d01-4004-982c-edecb232f322
📒 Files selected for processing (20)
app/src/components/settings/panels/AIPanel.tsxapp/src/components/settings/panels/__tests__/AIPanel.test.tsxapp/src/components/settings/panels/__tests__/azureDeployment.test.tsapp/src/components/settings/panels/azureDeployment.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsdocs/TEST-COVERAGE-MATRIX.mdscripts/i18n-find-english.ts
…erage gate (tinyhumansai#5213) Diff coverage went 64% -> 79% with the first batch; the gate needs 80%. The remaining uncovered lines were the custom-routing dialog's NON-Azure branches (catalog dropdown options, the non-Azure placeholder, the toggle label) plus its legacy hint. Two additions: - The existing dialog test now also types a catalog base model id, so the dialog's legacy-hint branch is exercised the same way the main selector's is. - A new test drives the dialog's non-Azure path end to end: selecting a plain OpenAI provider keeps the catalog dropdown and the 'Model' label (no Azure relabelling, no help text), the manual escape hatch still reaches an off-catalog id, and toggling back returns to the dropdown. That second one is worth having beyond the gate: tinyhumansai#5213 changes a shared model picker, and nothing else pinned that the non-Azure path came through unchanged. Ran locally: 59/59 pass in AIPanel.test.tsx.
|
@coderabbitai review |
✅ Action performedReview finished.
|
…yment copy (tinyhumansai#5213) Three CodeRabbit items. Minor — Azure Government (`*.openai.azure.us`) and Azure operated by 21Vianet (`*.openai.azure.cn`) were missing from the endpoint allowlist. They are separate DNS parents, so the commercial `.com` entries do not cover them and a sovereign tenant fell through to the exact "model not found" path this module exists to prevent. Both added, with tests. Minor — the deployment-name placeholder was `gpt-5.6-terra`, which reads as a base model identifier and contradicts the adjacent "This is not the model ID" guidance. Now `my-gpt-deployment`. The value is an illustrative identifier, not prose, so it is the same string in all 14 locales; i18n:check and i18n:english:check are clean. Minor — the per-workload dialog test only proved the text field rendered. A broken dialog-to-routing handoff would still have passed. It now completes the flow and asserts the persisted routing carries `{ kind: cloud, providerSlug: azure-foundry, model: workload-deployment }`. Ran locally: 67/67 pass across AIPanel.test.tsx + azureDeployment.test.ts.
|
@coderabbitai review |
✅ Action performedReview finished.
|
… gating Azure (tinyhumansai#5213) Three changes on top of the free-text deployment-name work, all in service of the same failure: an Azure Foundry user could not reach a working configuration. **Deduplicate the two pickers.** `CustomRoutingDialog` and `GlobalOwnModelSelector` each carried their own copy of the Azure entry-mode state machine, and the copies had already diverged: one had a `mono` field and a "select a model" placeholder option, the other did not; one rendered richer catalog option labels; the global card duplicated three `source?.kind !== 'local'` guards. `ai/ModelEntryField.tsx` now owns the behaviour once as `useModelEntryMode` + `<ModelEntryField>`, and the reconciled version is the stricter of the two everywhere. Provider-change handling collapses from three `setManualModelEntry(...)` branches per picker to a single `syncToEndpoint`. **A failed `/models` probe no longer blocks provider creation.** The add-provider flow rolled back, cleared the stored key and threw when the live probe rejected, which put the deployment-name field permanently out of reach for any provider that does not serve an OpenAI-shaped listing. The probe now informs: a rejection surfaces inline and unlocks "Add without verifying", scoped to that failure class only (a slug collision still blocks). The listing fills a dropdown; it was never a precondition for inference. **Point Azure users at the base URL that actually works.** Only `https://<resource>.<host>/openai/v1` serves `{base}/models` and accepts the resource key in the `authorization` header, which is the `bearer` auth style every custom provider is stored with; Azure's published v1 spec declares both an `api-key` and an `authorization` API-key scheme. The classic `api-version` surface serves neither, so a bare resource URL pasted from the portal fails the probe and then fails every call. `isAzureV1BaseUrl` drives an inline nudge in the provider editor when the host is Azure but the path is not the v1 base. Also drops `inference.ai.azure.com` / `models.ai.azure.com` from the Azure host list. Those are the Foundry *serverless* endpoints, which speak the Azure AI Model Inference API and key `model` on the model name, not a deployment name, so relabelling their field would mislead in exactly the place this change exists to clarify. And the manual-entry toggle now reads "Enter deployment name manually" on Azure rather than telling the user to enter a model ID. Tests: 7 new cases for the shared field (Azure vs catalog default, a still- loading catalog not stalling the Azure field, the probe-error branch, the legacy- value hint), 3 for `isAzureV1BaseUrl`, and 4 panel-level regressions covering the probe-failure escape, its scoping, and the endpoint nudge appearing and clearing.
|
@coderabbitai review Since the last review this PR now:
Also struck the "probe Azure's deployments listing" follow-up: the v1 data-plane spec has no deployments path, so it is an ARM control-plane call, not something the inference key can do. 25 tests across |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Changed files ==="
git diff --name-status origin/main...HEAD || git diff --name-status HEAD~1...HEAD
echo "=== Relevant source/test files ==="
fd -a -t f '^(ModelEntryField|AIPanel|azureDeployment).*' app/src 2>/dev/null || true
echo "=== Symbol outlines ==="
for f in \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/azureDeployment.ts \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/components/settings/panels/__tests__/azureDeployment.test.ts
do
if [ -f "$f" ]; then
echo "--- $f ---"
ast-grep outline "$f" --items all --view expanded || true
fi
done
echo "=== Focused diff ==="
git diff --unified=50 origin/main...HEAD -- \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/azureDeployment.ts \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/components/settings/panels/__tests__/azureDeployment.test.ts \
|| git diff --unified=50 HEAD~1...HEAD -- \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/azureDeployment.ts \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/components/settings/panels/__tests__/azureDeployment.test.tsLength of output: 120507 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Provider persistence / credential lifecycle (numbered) ==="
nl -ba app/src/components/settings/panels/AIPanel.tsx | sed -n '3420,3910p'
echo "=== Model-entry consumers and provider-selection transitions ==="
rg -n -C 8 'useModelEntryMode|syncToEndpoint|<ModelEntryField|clearCloudProviderKey|setCloudProviderKey|ProviderProbeError' \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Provider editor tests and API mock behavior ==="
rg -n -C 12 'Add Custom Provider|Add Provider|Edit Provider|Save Changes|clearCloudProviderKey|setCloudProviderKey|listProviderModels|flushCloudProviders' \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Relevant service API declarations ==="
fd -a -t f 'aiSettingsApi.ts' app/src | xargs -r -n1 sh -c '
echo "--- $0 ---"
rg -n -C 4 "setCloudProviderKey|clearCloudProviderKey|flushCloudProviders|listProviderModels" "$0"
'Length of output: 1476 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Provider persistence / credential lifecycle (numbered) ==="
awk 'NR >= 3420 && NR <= 3910 { printf "%6d\t%s\n", NR, $0 }' \
app/src/components/settings/panels/AIPanel.tsx
echo "=== Model-entry consumers and provider-selection transitions ==="
rg -n -C 8 'useModelEntryMode|syncToEndpoint|<ModelEntryField|clearCloudProviderKey|setCloudProviderKey|ProviderProbeError' \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Provider editor tests and API mock behavior ==="
rg -n -C 12 'Add Custom Provider|Add Provider|Edit Provider|Save Changes|clearCloudProviderKey|setCloudProviderKey|listProviderModels|flushCloudProviders' \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Relevant service API declarations ==="
fd -a -t f 'aiSettingsApi.ts' app/src | while IFS= read -r f; do
echo "--- $f ---"
rg -n -C 4 "setCloudProviderKey|clearCloudProviderKey|flushCloudProviders|listProviderModels" "$f"
doneLength of output: 127894 🏁 Script executed: #!/bin/bash
set -euo pipefail
awk 'NR >= 440 && NR <= 485 { printf "%6d\t%s\n", NR, $0 }' \
app/src/services/api/aiSettingsApi.ts
echo "=== Core credential lookup / provider auth fallback ==="
rg -n -C 6 'provider:|clear.*key|api_key|apiKey|auth_profiles|auth-profiles' \
src app/src \
-g '*.rs' -g '*.ts' -g '*.tsx' \
| head -n 360Length of output: 28138
P1 — Preserve the existing credential when an edited provider’s probe fails In A user who edits an existing working provider, enters a new key or endpoint, gets a probe failure, and then cancels has their original provider restored but no usable key. The same occurs before they choose “Add without verifying,” although that retry happens to write the entered key again. Rollback should preserve/restore the prior credential for edits, or only clear a just-created credential for a newly added provider. Please add a regression test for: edit existing provider → enter replacement key → probe fails → cancel → original provider and credential remain usable. The shared |
…on Azure (tinyhumansai#5213) Two follow-ups on the shared model-entry field. **The "Loading models" placeholder was lost for every provider.** It gated on the *effective* entry mode, but the panel clears the catalog before fetching, so `useManualEntry` is transiently true for everyone while the probe is in flight — which meant the dropdown-mode loading state never rendered and the field flashed straight to free text. Gate on the explicit mode instead: Azure still skips the wait (its catalog values are the wrong ones regardless), and everyone else gets the disabled placeholder back exactly as the routing dialog had it before. The prior test for this branch passed a non-empty catalog alongside `catalogLoading`, a combination the panel never produces, so it did not catch the regression. It now models the real shape. **The probe-error retry hint no longer tells an Azure user to enter a model id.** That copy is the one thing this change exists to stop asking for; Azure already gets the deployment-name help directly under the field.
|
@coderabbitai review Pushed The "Loading models" placeholder gated on the effective entry mode, but the panel clears the catalog before fetching, so Worth noting the test I had written for that branch passed a non-empty catalog alongside Also: the probe-error retry hint no longer tells an Azure user to "enter model id manually", which is the one thing this PR exists to stop asking for. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
app/src/components/settings/panels/__tests__/azureDeployment.test.ts (1)
102-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider pinning two more
isAzureV1BaseUrlshapes.Both are plausible real inputs and neither is currently covered: a sovereign-cloud v1 base (
https://my-res.openai.azure.us/openai/v1) and a URL carrying a query string (https://my-res.openai.azure.com/openai/v1?api-version=2024-10-21), which the path regex rejects because the query is kept in the sliced path. Pinning them documents whether the amberazureV1EndpointHintis expected to appear in those cases.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/__tests__/azureDeployment.test.ts` around lines 102 - 125, Extend the isAzureV1BaseUrl tests to cover a sovereign-cloud v1 endpoint using the azure.us host and a v1 endpoint with an api-version query string. Assert the expected boolean result for each case, documenting whether azureV1EndpointHint should recognize both URL shapes.app/src/components/settings/panels/ai/ModelEntryField.tsx (1)
64-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd grep-friendly diagnostics for the mode transitions.
toggleManualEntryandsyncToEndpointare the two state transitions of this new shared flow and neither is logged, while the surrounding panel logs consistently under[ai-settings]. A one-lineconsole.debugper transition (endpoint host classification + resulting mode, no secrets) makes Azure misdetection reports diagnosable.As per coding guidelines, "New or changed flows must include verbose, grep-friendly diagnostics for entry/exit, branches, external calls, retries/timeouts, transitions, and errors."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/ai/ModelEntryField.tsx` around lines 64 - 72, Add one-line console.debug diagnostics to toggleManualEntry and syncToEndpoint, using the existing “[ai-settings]” prefix. Log the endpoint host classification and resulting manual-entry mode without exposing secrets, covering both state transitions.Source: Coding guidelines
app/src/components/settings/panels/AIPanel.tsx (1)
3596-3617: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLog the probe-bypass branch.
The probe-failure path logs under
[ai-settings], but taking the newskipProbeshortcut — creating a provider with no live verification — produces no diagnostic at all, which is the one outcome support will need to spot in a log.As per coding guidelines, new flows must include grep-friendly diagnostics for branches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/AIPanel.tsx` around lines 3596 - 3617, Add a grep-friendly diagnostic for the skipProbe branch in the provider setup flow, alongside the existing listProviderModels probe handling. When opts?.skipProbe is true and verification is bypassed, log that the provider was created without live probing, including the provider slug or label; preserve the existing probe execution and failure logging behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx`:
- Around line 540-574: Update the AIPanel provider-add flow and its regression
test so a failed model probe cannot be bypassed for legacy Azure base URLs
ending in /openai. Require the normalized /openai/v1 endpoint before showing or
accepting “Add without verifying,” unless the implementation also supplies the
required legacy Azure authentication/request format; preserve the bypass for
providers whose configured endpoint supports the existing bearer-auth inference
path.
- Around line 615-637: Extend the test around AIPanel’s submitProvider flow to
preserve the duplicate-slug validation assertions, then add a separate
unique-provider submission that reaches the key-write/persist path and rejects.
Mock that persistence failure and assert the “Add without verifying” action is
still absent after submission, ensuring the catch branch handles a non-probe
failure without offering the verification bypass.
In `@app/src/components/settings/panels/ai/ModelEntryField.tsx`:
- Around line 126-155: Update the loading-state condition in ModelEntryField’s
showLoadingSelect logic to use the user’s explicit mode.manualEntry choice
instead of the derived useManualEntry value. Ensure non-manual catalog loads
render the loading select while loading, and preserve manual text entry whenever
mode.manualEntry is explicitly enabled.
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 2303-2321: Replace the hard-coded Claude Code help paragraph in
the model field within AIPanel with a useT()-based translation key, including
interpolation or markup support for the inline code elements. Add the
corresponding localized string to the locale files already touched by this
change, preserving the existing guidance and formatting.
- Around line 3786-3792: Update submitProvider to reset probeFailed alongside
submitError at the start of each submission attempt. Preserve the existing
ProviderProbeError-specific behavior so the bypass option is only shown when the
current attempt’s failure is probe-related, not for later slug or key-write
failures.
---
Nitpick comments:
In `@app/src/components/settings/panels/__tests__/azureDeployment.test.ts`:
- Around line 102-125: Extend the isAzureV1BaseUrl tests to cover a
sovereign-cloud v1 endpoint using the azure.us host and a v1 endpoint with an
api-version query string. Assert the expected boolean result for each case,
documenting whether azureV1EndpointHint should recognize both URL shapes.
In `@app/src/components/settings/panels/ai/ModelEntryField.tsx`:
- Around line 64-72: Add one-line console.debug diagnostics to toggleManualEntry
and syncToEndpoint, using the existing “[ai-settings]” prefix. Log the endpoint
host classification and resulting manual-entry mode without exposing secrets,
covering both state transitions.
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 3596-3617: Add a grep-friendly diagnostic for the skipProbe branch
in the provider setup flow, alongside the existing listProviderModels probe
handling. When opts?.skipProbe is true and verification is bypassed, log that
the provider was created without live probing, including the provider slug or
label; preserve the existing probe execution and failure logging behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 85abd7f1-7b95-4b95-9edc-33af6f7d389a
📒 Files selected for processing (21)
app/src/components/settings/panels/AIPanel.tsxapp/src/components/settings/panels/__tests__/AIPanel.test.tsxapp/src/components/settings/panels/__tests__/ModelEntryField.test.tsxapp/src/components/settings/panels/__tests__/azureDeployment.test.tsapp/src/components/settings/panels/ai/ModelEntryField.tsxapp/src/components/settings/panels/azureDeployment.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsdocs/TEST-COVERAGE-MATRIX.md
🚧 Files skipped from review as they are similar to previous changes (12)
- app/src/lib/i18n/hi.ts
- app/src/components/settings/panels/azureDeployment.ts
- app/src/lib/i18n/de.ts
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/ru.ts
- app/src/lib/i18n/id.ts
- app/src/lib/i18n/pt.ts
- app/src/lib/i18n/it.ts
- app/src/lib/i18n/bn.ts
- app/src/lib/i18n/ko.ts
- docs/TEST-COVERAGE-MATRIX.md
- app/src/lib/i18n/pl.ts
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Current revision and incremental diff ==="
git status --short
git show --stat --oneline --decorate --no-renames f02a64e4d
git diff --unified=45 f02a64e4d^ f02a64e4d -- \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/lib/i18n/en.ts
echo "=== Relevant file map ==="
fd -a -t f '^(ModelEntryField|AIPanel|azureDeployment).*' app/src/components/settings/panels
echo "=== Component and test outlines ==="
for f in \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
do
[ -f "$f" ] || continue
echo "--- $f ---"
ast-grep outline "$f" --items all --view expanded || true
done
echo "=== State-mode, retry-copy, and credential rollback call contexts ==="
rg -n -C 12 \
'catalogLoading|useManualEntry|entryMode|Loading models|ProviderProbeError|Add without verifying|enterModelIdManuallyAction|clearCloudProviderKey|setCloudProviderKey|rollback|previousProvider' \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/lib/i18n/en.tsLength of output: 118872
One previously reported issue remains: P1 — Failed edits still erase the existing provider credential In The new commit does not touch this flow. Please preserve/restore the old credential for an edited provider, or avoid destructive credential replacement until verification succeeds. Add the regression test for: existing provider → replacement key → failed probe → cancel → original provider and credential remain usable. ✅ Action performedReview finished.
|
…mpt (tinyhumansai#5213) Review follow-ups, all on the "add without verifying" escape added for tinyhumansai#5213. **The bypass is withheld for an endpoint already known to be unusable.** Skipping verification is a bet that the provider works despite an unreadable listing. On an Azure host that is not the `/openai/v1` base that bet is already lost: `{base}/chat/completions` is not a route Azure serves there, and the stored bearer auth is the wrong header for the `api-version` surface. Offering the bypass would manufacture a dead provider, so the inline nudge is now the only way forward for those URLs. The regression test that previously blessed saving a legacy base URL is corrected to the v1 base, and a new test pins the withholding. **`probeFailed` is cleared at the start of every attempt.** It previously persisted for the dialog's lifetime, so once any probe rejected, "add without verifying" stayed on screen even when a later attempt failed for a slug collision or a key write — exactly the distinction `ProviderProbeError` was introduced to preserve. **The bypass-scoping test now exercises the real catch path.** The existing case asserted a disabled submit button, which never reaches `submitProvider`'s catch. Two new cases drive a rejecting credential write: one asserts the bypass is not offered for that failure class, the other that a prior probe failure does not leave it behind. Also lifts the Claude Code model help text out of hard-coded English into `settings.ai.claudeCode.modelHelp` across all 14 locales. It predates this PR but sits in the diff, and the repo rule is that all UI text goes through `useT()`.
|
@coderabbitai review
Major — bypass for a legacy Azure base URL. Agreed, and fixed at the source rather than in the test. Skipping verification is a bet that the provider works despite an unreadable listing; on an Azure host that is not the Minor — exercise an actual non-probe submit failure. Correct, the slug case never reached the catch. Two new cases drive a rejecting Minor — Minor — loading placeholder. Already shipped in Minor — Claude Code help text hard-coded. Fair even though it predates this PR, since it sits in the diff. Now Gates: typecheck clean, ESLint 0 errors, |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
commit="7cf7806f6"
echo "=== Commit and changed files ==="
git show --stat --oneline --no-renames "$commit"
git diff --name-status "${commit}^" "$commit"
echo "=== Focused incremental diff ==="
git diff --unified=55 "${commit}^" "$commit" -- \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx \
app/src/components/settings/panels/azureDeployment.ts \
app/src/lib/i18n \
scripts/i18n-find-english.ts
echo "=== Relevant source/test structure ==="
for f in \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx
do
[ -f "$f" ] || continue
echo "--- $f ---"
ast-grep outline "$f" --items all --view expanded
done
echo "=== Provider submit, rollback, and bypass control flow ==="
rg -n -C 18 \
'knownUnusableEndpoint|ProviderProbeError|probeFailed|skipProbe|Add without verifying|setCloudProviderKey|clearCloudProviderKey|previousProvider|previous.*[Kk]ey|rollback|submitError' \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Model field loading/error flow ==="
rg -n -C 12 \
'catalogLoading|manualEntry|useManualEntry|Loading models|catalogError|enterModelIdManuallyAction|enterDeploymentNameManuallyAction' \
app/src/components/settings/panels/ai/ModelEntryField.tsx \
app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx
echo "=== Claude Code model-help localization coverage ==="
rg -n -C 3 'claudeCode|modelHelp' app/src/lib/i18n
echo "=== Search for the touched translation key in source ==="
rg -n -C 4 'settings\.ai\.claudeCode\.modelHelp|claudeCode\.modelHelp' app/srcLength of output: 190622 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== CloudProviderEditor: exact submission and rollback block ==="
nl -ba app/src/components/settings/panels/AIPanel.tsx | sed -n '3480,3650p'
echo "=== CloudProviderEditor: state initialization and endpoint classification ==="
nl -ba app/src/components/settings/panels/AIPanel.tsx | sed -n '3370,3485p'
echo "=== Credential rollback regression test candidates ==="
rg -n -i -C 12 \
'edit.*provider|existing provider|replacement key|original.*key|failed.*probe|probe.*fail|cancel' \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Incremental diff: source and relevant tests only ==="
git diff --unified=25 7cf7806f6^ 7cf7806f6 -- \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Key API semantics and credential-read capabilities ==="
fd -a -t f 'aiSettingsApi.ts' app/src | while IFS= read -r f; do
echo "--- $f ---"
rg -n -C 4 'CloudProviderKey|provider.*key|key.*provider|has_api_key' "$f"
doneLength of output: 1459 🏁 Script executed: #!/bin/bash
set -euo pipefail
print_range() {
local file="$1" start="$2" end="$3"
awk -v start="$start" -v end="$end" 'NR >= start && NR <= end { printf "%6d %s\n", NR, $0 }' "$file"
}
echo "=== CloudProviderEditor: exact submission and rollback block ==="
print_range app/src/components/settings/panels/AIPanel.tsx 3480 3650
echo "=== CloudProviderEditor: state initialization and endpoint classification ==="
print_range app/src/components/settings/panels/AIPanel.tsx 3370 3485
echo "=== Credential rollback regression test candidates ==="
rg -n -i -C 12 \
'edit.*provider|existing provider|replacement key|original.*key|failed.*probe|probe.*fail|cancel' \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Incremental diff: source and relevant tests only ==="
git diff --unified=25 7cf7806f6^ 7cf7806f6 -- \
app/src/components/settings/panels/AIPanel.tsx \
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
echo "=== Key API semantics and credential-read capabilities ==="
fd -a -t f 'aiSettingsApi.ts' app/src | while IFS= read -r f; do
echo "--- $f ---"
rg -n -C 4 'CloudProviderKey|provider.*key|key.*provider|has_api_key' "$f"
doneLength of output: 52823
In The new tests correctly cover legacy Azure bypass withholding, key-write failures, and
This needs a non-destructive verification design—for example, a temporary/probe credential or an atomic backend operation that retains/restores the old secret. Simply skipping The Azure ✅ Action performedReview finished.
|
Summary
modelfield of the outbound Azure OpenAI call./modelscatalog entry; seeding a base model id is precisely what produced "Model not found"./modelsprobe no longer blocks provider creation — that gate was what put the deployment-name field out of reach for the users who most need it. It stays blocking for an endpoint already known to be unusable./openai/v1base URL, which is the only Azure base that behaves like every other provider OpenHuman stores.Problem
Azure AI Foundry separates the base model id a deployment was created from (
gpt-5.6-terra-2026-07-09) from the user-chosen deployment name (gpt-5.6-terra) that actually routes the request. Azure's OpenAI-compatible surface keys the request body'smodelfield on the deployment name, so a value taken from the provider's/modelscatalog returns "Model not found" for every inference call.This is not a heuristic reading of the docs. Azure's published v1 data-plane spec (
specification/ai/data-plane/OpenAI.v1/azure-v1-v1-generated.json) settles each half:servers[0].url={endpoint}/openai/v1{base}/modelsexist?paths./modelsand/models/{model}securitySchemesdeclares bothapi-keyandauthorization, so the resource key works as a bearer token on this baseSo the catalog can never contain the routing value except by coincidence.
Tracing the value end to end shows routing was never at fault: the
<model>half of a"<slug>:<model>"provider string passes throughresolve_cloud_slugunmodified (src/openhuman/inference/provider/factory.rs:2071) and lands verbatim asCrateOpenAiConfig.model(factory.rs:2329), which becomes themodelfield ofPOST {base_url}/chat/completions. The correct value already reached the wire.The defect was in the settings UI, which sourced that value exclusively from the probed catalog — and, one layer earlier, in the add-provider flow, which refused to create a provider whose
/modelslisting could not be read.Solution
azureDeployment.ts—isAzureFoundryEndpoint()classifies a connection by endpoint host (*.openai.azure.com,*.services.ai.azure.com,*.cognitiveservices.azure.com, plus the sovereign*.openai.azure.us/*.openai.azure.cn), matching only on a dot boundary so a lookalike such asopenai.azure.com.evil.teststays unmatched.endpointHost()mirrors the Rustendpoint_hosthelper insrc/openhuman/config/schema/cloud_providers.rs, including its tolerance for a missing scheme, so both sides classify a stored endpoint identically.Detection is by host, not slug, and that is deliberate. Azure is reachable today only through the generic "Add cloud provider" flow — there is no
azureentry inBUILTIN_CLOUD_PROVIDERSon either the TS or Rust side — so the user picks the slug (azure,azure-foundry,my-azure, …) and the host is the one stable signal.inference.ai.azure.com/models.ai.azure.comare deliberately not in that list. They are the Foundry serverless endpoints, which speak the Azure AI Model Inference API at{endpoint}/models/chat/completionsand keymodelon the model name rather than a deployment name — relabelling their field would mislead in exactly the place this change exists to clarify.ai/ModelEntryField.tsx(new) —useModelEntryMode+<ModelEntryField>own the entry-mode state machine and the whole model-field area (free text, catalog dropdown, mode toggle, loading and probe-error branches, Azure help and legacy-value hint) for both pickers. It initialises to free text for an Azure connection and re-derives on provider change via a singlesyncToEndpointcall, so Azure users land on free text while every other provider keeps its existing dropdown-first behaviour with a manual escape hatch.A failed
/modelsprobe no longer blocks creation. The add-provider flow used to roll back the provider list, clear the stored key and throw when the live probe rejected. For a provider that serves no OpenAI-shaped listing that made the connection uncreatable, and the deployment-name field unreachable no matter how good the field was. A rejection now surfaces inline and unlocks "Add without verifying", scoped to that failure class via a dedicatedProviderProbeErrorso a slug collision or a failed key write still blocks, and cleared at the start of every attempt so a stale probe failure cannot leak the bypass into an unrelated one.The bypass is deliberately withheld for an endpoint already known to be unusable. Skipping verification is a bet that the provider works despite an unreadable listing; on an Azure host that is not the
/openai/v1base that bet is already lost, since{base}/chat/completionsis not a route Azure serves there and the stored bearer auth is the wrong header. Offering it would manufacture a dead provider, so the inline nudge below is the only way forward for those URLs. The listing otherwise fills a dropdown; it was never a precondition for inference.Base-URL guidance. Only
https://<resource>.<host>/openai/v1serves{base}/modelsand accepts the resource key in theauthorizationheader — thebearerauth style every custom provider is stored with. The classicapi-versionsurface serves neither, so a bare resource URL pasted from the portal fails the probe and then fails every call.isAzureV1BaseUrldrives an inline nudge when the host is Azure but the path is not the v1 base.Migration. The hint fires when the stored value is verbatim a catalog entry. That fingerprint is exact rather than heuristic: before this change the dropdown was the only way to set the value, so catalog membership is precisely the signature of a connection configured the broken way. It stays a hint rather than an error because a user is free to name a deployment after its base model, in which case the value is already correct and confirming it is a no-op.
Trade-off considered and rejected: adding a first-class
azurebuiltin preset. That would touch thebuiltin_cloud_supports_responses_apidrift guard incloud_providers.rsand needs a per-user endpoint (every Azure resource has its own hostname), which is a larger change than this bug requires. Host detection covers the reported flow today; the preset is listed as follow-up.Review items addressed
monoon one text field but not the other, a "select a model" placeholder option on one dropdown only, richer catalog option labels on one, three redundantsource?.kind !== 'local'guards on the other). The shared module reconciles to the stricter behaviour in every case.*.openai.azure.usadded, and*.openai.azure.cnalongside it.my-gpt-deployment, not a model-id-shaped example.settings.ai.enterDeploymentNameManuallyActionkey across all 14 locales; Azure connections no longer tell the user to enter a model ID.api-keyauthentication. Real, but narrower than stated, and the earlier reply on that thread overstated it. Azure's v1 spec declaresauthorizationas an accepted API-key scheme and Microsoft's v1 samples drive the stock OpenAI SDK with a plain resource key, sobeareris not gating on the/openai/v1base. It is gating on the classicapi-versionbase — which this PR now addresses from both sides: the endpoint nudge steers users to the base that works, and "Add without verifying" stops the probe from trapping anyone who stays on the old one. A dedicatedazure-api-keyauth style remains worthwhile follow-up for classic-surface users, plumbed through the provider schema, editor, probe and inference path with tests on each; it is its own PR.monomissing on the global card's text field. Fixed by the shared component.Submission Checklist
azureDeployment.test.ts, 7 in the newModelEntryField.test.tsx, and 7 UI regressions inAIPanel.test.tsx. The Azure UI tests were verified to fail against the pre-fixAIPanel.tsx.pnpm test:coveragewas not run locally: the full suite is barred by the shared-machine directive inWORKFLOW-RULES.md, so thecoverage-gatelane is the authority here.13.3.3 Azure deployment name (off-catalog model id)indocs/TEST-COVERAGE-MATRIX.mdextended to the new files and branches.## Related.listProviderModelsand drive existing UI only.docs/RELEASE-MANUAL-SMOKE.mdhas no BYOK-provider-configuration entry, and this change adds no release-cut surface.Closes #NNNin the## Relatedsection.Impact
Desktop UI only (
app/src). No Rust, no RPC, no config-schema or wire-format change, so there is nothing to migrate on disk and no forward/backward compatibility concern.The one behaviour change reaching non-Azure users is that a failed
/modelsprobe is now recoverable instead of fatal. That is strictly more permissive: the happy path is unchanged, the failure is still shown, and creating the provider anyway takes a second explicit click. Non-Azure providers otherwise keep their dropdown-first behaviour, plus an "Enter model ID manually" button that also unblocks any provider whose listing omits a model the user is entitled to.No secrets or PII are logged. Security notes: the Azure host match is anchored on a dot boundary so a lookalike domain cannot be classified as Azure, and the probe-skip path does not weaken any credential handling — the key is written exactly as before, and is still cleared on a rejected probe unless the user explicitly proceeds.
Related
13.3.3azure-api-keyauth style (api-key: <key>instead ofAuthorization: Bearer <key>), for resource-key users on the classicapi-versionsurface. Bearer is retained for Entra tokens and for the v1 base.azurebuiltin provider chip (needs a per-user endpoint via the existingendpointKeyModeused by OMLX, plus abuiltin_cloud_supports_responses_apientry).Probe Azure's deployments listing so the dropdown can offer real deployment names.Not feasible as written — the v1 data-plane spec has no deployments path; listing deployments is an ARM control-plane call needing a management-scope token, not the inference key. Struck rather than carried forward./openai/v1probe passing and the resource key working as a bearer token are the two to confirm.Explain Like I'm 5
Imagine you have a puppy. The shop that sold him wrote "Golden Retriever, born July 2026" on his papers. That is his kind. But you named him Rex, and Rex is the only name he answers to.
Our app had a rule: you may only call the puppy by the name written on the papers. It gave you a little list to pick from, and that list only had kinds, never names. So the app would call out "Golden Retriever, born July 2026!" and the puppy would just sit there. The app shrugged and said "I can't find that dog."
Worse, before it would even let you register the puppy, it insisted on reading the shop's list of kinds out loud. If the shop had no such list, the app refused to register him at all. You never even got to the part where you type his name.
The fix, in three pieces. You can now type his real name in a box that asks "What did you name him?", and the app calls out whatever you type. The app stopped guessing a name for you off the list, because that guessing was exactly what made it shout the wrong thing. And it no longer refuses to register a puppy just because it could not read the shop's list — it tells you it could not read it, and lets you go ahead anyway. It also tidied up: it used to keep two slightly different copies of this whole box, and the copies had started disagreeing with each other, so now there is one.
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/5213-azure-deployment-name7cf7806f6Validation Run
npx prettier --writeproduced no further changes on re-run).pnpm typecheck— clean.azureDeployment.test.ts11/11,ModelEntryField.test.tsx8/8,AIPanel.test.tsx66/66. Whole settings-panel suite green: 48 files, 680 passed / 1 skipped.pnpm i18n:check(missing: 0,extra: 0in all 13 locales) andpnpm i18n:english:check(total unexpected English: 0). No em dashes in any translation value.react-hooks/set-state-in-effectwarnings inAIPanel.tsxare pre-existing and untouched.WORKFLOW-RULES.md,cargo test/cargo check --testswere not run; CI is the authority for the Rust lanes.app/src-taurichange.Validation Blocked
command:pnpm test:coverage(and any Rust test-target compile)error:not attemptedimpact:barred by the shared-machine directive inWORKFLOW-RULES.md. Diff coverage is verified by thecoverage-gateCI lane instead. No Rust changed, so the Rust lanes are unaffected by this PR.Behavior Changes
/modelscatalog is populated; Azure connections default to that mode, are relabelled "Deployment name", and are never auto-seeded from the catalog./modelsprobe no longer prevents adding a cloud provider — it is reported inline and the user may proceed explicitly, except on an Azure endpoint that is not the/openai/v1base, where proceeding could only produce a dead provider./openai/v1base shows an inline correction and withholds the verification bypass.Parity Contract
useManualEntryfalls back to free text whenever the catalog is empty, which is exactly the pre-existing behaviour for an empty listing, and the loading affordance is gated on the explicit mode so a dropdown-mode provider still shows it while probing. The probe-skip path is gated on a typedProviderProbeError, so no other submit failure can reach it — pinned by two regression tests that drive a rejecting credential write. A further test asserts the non-Azure dropdown path is unchanged and that no Azure labelling leaks into it.Duplicate / Superseded PR Handling
azure,deployment,foundry, and5213in titles and branch names.