Skip to content

fix(ai-settings): allow free-text Azure deployment names (#5213) - #5222

Merged
senamakel merged 8 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/5213-azure-deployment-name
Jul 29, 2026
Merged

fix(ai-settings): allow free-text Azure deployment names (#5213)#5222
senamakel merged 8 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/5213-azure-deployment-name

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Azure AI Foundry connections take a free-text Deployment name, used verbatim as the model field of the outbound Azure OpenAI call.
  • Both model pickers (the global "Use Your Own Models" card and the per-workload routing dialog) share one implementation of that field, so the Azure behaviour cannot drift between them.
  • Azure connections no longer auto-select the first /models catalog entry; seeding a base model id is precisely what produced "Model not found".
  • A failed /models probe 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.
  • The provider editor nudges Azure users to the /openai/v1 base URL, which is the only Azure base that behaves like every other provider OpenHuman stores.
  • Existing connections are prompted, not rewritten: a stored value that is verbatim a catalog entry shows an inline hint. Nothing on disk is migrated behind the user's back.
  • Frontend-only. No Rust change was required (see Problem).

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's model field on the deployment name, so a value taken from the provider's /models catalog 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:

Question What the spec says
What is the OpenAI-compatible base? servers[0].url = {endpoint}/openai/v1
Does {base}/models exist? yes — paths./models and /models/{model}
Does it list deployments? no — it lists base + fine-tuned models. There is no deployments path on this data plane at all; listing deployments is an ARM control-plane call
How does it authenticate? securitySchemes declares both api-key and authorization, so the resource key works as a bearer token on this base

So 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 through resolve_cloud_slug unmodified (src/openhuman/inference/provider/factory.rs:2071) and lands verbatim as CrateOpenAiConfig.model (factory.rs:2329), which becomes the model field of POST {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 /models listing could not be read.

Solution

azureDeployment.tsisAzureFoundryEndpoint() 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 as openai.azure.com.evil.test stays unmatched. endpointHost() mirrors the Rust endpoint_host helper in src/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 azure entry in BUILTIN_CLOUD_PROVIDERS on 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.com are deliberately not in that list. They are the Foundry serverless endpoints, which speak the Azure AI Model Inference API at {endpoint}/models/chat/completions and key model on 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 single syncToEndpoint call, so Azure users land on free text while every other provider keeps its existing dropdown-first behaviour with a manual escape hatch.

A failed /models probe 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 dedicated ProviderProbeError so 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/v1 base that bet is already lost, since {base}/chat/completions is 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/v1 serves {base}/models and accepts the resource key in the authorization header — the bearer auth style every custom provider is stored with. 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 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 azure builtin preset. That would touch the builtin_cloud_supports_responses_api drift guard in cloud_providers.rs and 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

  • CodeRabbit, Major — duplicated Azure state machine across the two pickers. Fixed, not deferred. The two copies had already diverged (mono on one text field but not the other, a "select a model" placeholder option on one dropdown only, richer catalog option labels on one, three redundant source?.kind !== 'local' guards on the other). The shared module reconciles to the stricter behaviour in every case.
  • CodeRabbit — Azure Government host. *.openai.azure.us added, and *.openai.azure.cn alongside it.
  • CodeRabbit — deployment-shaped placeholder. my-gpt-deployment, not a model-id-shaped example.
  • CodeRabbit — Azure-specific action label. New settings.ai.enterDeploymentNameManuallyAction key across all 14 locales; Azure connections no longer tell the user to enter a model ID.
  • CodeRabbit — persist the per-workload deployment name in the regression test. The test asserts the saved routing, not just the field.
  • Codex, P1 — Azure api-key authentication. Real, but narrower than stated, and the earlier reply on that thread overstated it. Azure's v1 spec declares authorization as an accepted API-key scheme and Microsoft's v1 samples drive the stock OpenAI SDK with a plain resource key, so bearer is not gating on the /openai/v1 base. It is gating on the classic api-version base — 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 dedicated azure-api-key auth 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.
  • Greptile — mono missing on the global card's text field. Fixed by the shared component.
  • Greptile — toggle copy. Withdrawn by the reviewer; the label is action-shaped, and Azure now gets its own action string anyway.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — 11 unit tests in azureDeployment.test.ts, 7 in the new ModelEntryField.test.tsx, and 7 UI regressions in AIPanel.test.tsx. The Azure UI tests were verified to fail against the pre-fix AIPanel.tsx.
  • Diff coverage ≥ 80% — changed lines are frontend-only and covered by the three Vitest files above, including the loading, probe-error and legacy-hint branches of the new shared module. pnpm test:coverage was not run locally: the full suite is barred by the shared-machine directive in WORKFLOW-RULES.md, so the coverage-gate lane is the authority here.
  • Coverage matrix updated — row 13.3.3 Azure deployment name (off-catalog model id) in docs/TEST-COVERAGE-MATRIX.md extended to the new files and branches.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related.
  • No new external network dependencies introduced — N/A: no new network calls; the tests mock listProviderModels and drive existing UI only.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: docs/RELEASE-MANUAL-SMOKE.md has no BYOK-provider-configuration entry, and this change adds no release-cut surface.
  • Linked issue closed via Closes #NNN in the ## Related section.

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 /models probe 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

  • Closes: [Bug] Azure Foundry: "Model not found" when deployment name differs from model name — allow free-text deployment name input #5213
  • Coverage matrix feature IDs: 13.3.3
  • Follow-up PR(s)/TODOs:
    • An azure-api-key auth style (api-key: <key> instead of Authorization: Bearer <key>), for resource-key users on the classic api-version surface. Bearer is retained for Entra tokens and for the v1 base.
    • A first-class azure builtin provider chip (needs a per-user endpoint via the existing endpointKeyMode used by OMLX, plus a builtin_cloud_supports_responses_api entry).
    • 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.
    • Worth verifying against a live Azure resource: every Azure claim here is spec- and doc-derived, since no Azure resource was available. The /openai/v1 probe 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

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/5213-azure-deployment-name
  • Commit SHA: 7cf7806f6

Validation Run

  • Prettier — clean on every changed file (npx prettier --write produced no further changes on re-run).
  • pnpm typecheck — clean.
  • Focused tests: azureDeployment.test.ts 11/11, ModelEntryField.test.tsx 8/8, AIPanel.test.tsx 66/66. Whole settings-panel suite green: 48 files, 680 passed / 1 skipped.
  • pnpm i18n:check (missing: 0, extra: 0 in all 13 locales) and pnpm i18n:english:check (total unexpected English: 0). No em dashes in any translation value.
  • ESLint on changed files: 0 errors. The 2 react-hooks/set-state-in-effect warnings in AIPanel.tsx are pre-existing and untouched.
  • Rust fmt/check (if changed): N/A — no Rust source changed. Per the shared-machine directive in WORKFLOW-RULES.md, cargo test / cargo check --tests were not run; CI is the authority for the Rust lanes.
  • Tauri fmt/check (if changed): N/A — no app/src-tauri change.

Validation Blocked

  • command: pnpm test:coverage (and any Rust test-target compile)
  • error: not attempted
  • impact: barred by the shared-machine directive in WORKFLOW-RULES.md. Diff coverage is verified by the coverage-gate CI lane instead. No Rust changed, so the Rust lanes are unaffected by this PR.

Behavior Changes

  • The model field accepts a free-text value even when the provider's /models catalog is populated; Azure connections default to that mode, are relabelled "Deployment name", and are never auto-seeded from the catalog.
  • A rejected /models probe 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/v1 base, where proceeding could only produce a dead provider.
  • Adding an Azure endpoint that is not the /openai/v1 base shows an inline correction and withholds the verification bypass.
  • The Claude Code model help text in the routing dialog is now translated rather than hard-coded English.
  • User-visible effect: Azure Foundry users can enter their deployment name and run inference successfully. Users of other providers see one additional "Enter model ID manually" button and a recoverable rather than fatal probe failure.

Parity Contract

  • Legacy behavior preserved: non-Azure providers still render the catalog dropdown first, with the same option list, the same "keep an unlisted current value selectable" behaviour, and the same catalog auto-seed on the global card. A successful probe follows exactly the previous path.
  • Guard/fallback/dispatch parity checks: useManualEntry falls 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 typed ProviderProbeError, 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

  • Duplicate PR(s): none found. Searched open PRs for azure, deployment, foundry, and 5213 in titles and branch names.
  • Canonical PR: this one.
  • Resolution: N/A

…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.
@M3gA-Mind
M3gA-Mind requested a review from a team July 27, 2026 15:18
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 069975ec-ac4d-4559-9381-9979dae3cedb

📥 Commits

Reviewing files that changed from the base of the PR and between e990655 and 7cf7806.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx
  • app/src/components/settings/panels/ai/ModelEntryField.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
📝 Walkthrough

Walkthrough

Azure 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.

Changes

Azure deployment routing

Layer / File(s) Summary
Azure endpoint and model detection
app/src/components/settings/panels/azureDeployment.ts, app/src/components/settings/panels/__tests__/azureDeployment.test.ts
Adds endpoint normalization, Azure Foundry and v1 detection, catalog matching, and focused tests.
Deployment-name selector flows
app/src/components/settings/panels/ai/ModelEntryField.tsx, app/src/components/settings/panels/AIPanel.tsx
Adds shared manual/catalog entry modes, Azure deployment-name guidance, catalog auto-selection suppression, mode synchronization, and verbatim routing persistence.
Provider probe fallback
app/src/components/settings/panels/AIPanel.tsx
Distinguishes /models probe failures, supports adding providers without verification, and displays Azure endpoint hints.
Regression coverage
app/src/components/settings/panels/__tests__/*
Covers Azure and non-Azure selector behavior, persistence, mode switching, endpoint guidance, probe failures, and submission errors.
Localized UI and coverage records
app/src/lib/i18n/*, scripts/i18n-find-english.ts, docs/TEST-COVERAGE-MATRIX.md
Adds translated UI messages, allowlists the English placeholder, and records feature coverage.

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
Loading

Suggested labels: feature, bug

Suggested reviewers: graycyrus

Poem

I’m a rabbit with a deployment name,
Typed in neatly, no catalog game.
Azure hops where model routes go,
Probe or skip—the path is clear.
Thump, thump—saved verbatim! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy #5213 by adding Azure deployment-name free text, verbatim routing, off-catalog support, and prompts for existing values.
Out of Scope Changes check ✅ Passed The added tests, translations, docs, and helper code are directly tied to the Azure deployment-name feature and its UI flow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: enabling free-text Azure deployment names in AI settings.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread app/src/components/settings/panels/azureDeployment.ts
@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes Azure AI Foundry deployment-name routing by switching the model field to free text for Azure connections, suppressing the auto-seed from the probed /models catalog, and making a failed probe recoverable rather than fatal. It also extracts the model-field state machine into a single shared ModelEntryField component used by both the global card and the per-workload routing dialog.

  • azureDeployment.ts — New pure-function helpers detect Azure endpoints by host (with dot-boundary anchor), distinguish the v1 base from the legacy api-version surface, and fingerprint pre-fix values by catalog membership.
  • ai/ModelEntryField.tsx — New shared component owns the entry-mode state machine (free text vs. catalog dropdown, toggle, loading/error branches, Azure relabelling and legacy-value hint) for both pickers, preventing the two implementations from drifting again.
  • AIPanel.tsxProviderProbeError class scopes the probe-failure signal so only that failure class unlocks "Add without verifying"; syncToEndpoint is called on every provider-picker change to re-derive the default mode; Azure auto-seed suppression is gated on isAzureFoundryEndpoint.

Confidence Score: 4/5

Safe to merge after addressing the stale probe-failure state in CloudProviderEditor; all other changes are well-tested and bounded to the frontend settings UI.

The stale probeFailed flag in CloudProviderEditor can surface the bypass button for a URL that was never probe-tested, potentially leading users to skip a verification that would have succeeded.

Files Needing Attention: AIPanel.tsx — specifically the CloudProviderEditor component's endpoint and apiKey onChange handlers, which do not clear probeFailed.

Important Files Changed

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

Comment thread app/src/components/settings/panels/AIPanel.tsx Outdated
Comment thread app/src/components/settings/panels/AIPanel.tsx Outdated
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

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.

@M3gA-Mind M3gA-Mind closed this Jul 27, 2026
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Reopening at the human's request — keeping this open.

@M3gA-Mind M3gA-Mind reopened this Jul 27, 2026
@M3gA-Mind
M3gA-Mind marked this pull request as draft July 27, 2026 15:27
…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.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Jul 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
app/src/components/settings/panels/AIPanel.tsx (1)

2001-2010: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No diagnostics on the new Azure-detection state transitions.

manualModelEntry is a new state machine driven entirely by isAzureFoundryEndpoint, 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 uses console.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

📥 Commits

Reviewing files that changed from the base of the PR and between 44783ce and a8ed097.

📒 Files selected for processing (20)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/components/settings/panels/__tests__/azureDeployment.test.ts
  • app/src/components/settings/panels/azureDeployment.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • scripts/i18n-find-english.ts

Comment thread app/src/components/settings/panels/__tests__/AIPanel.test.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx Outdated
Comment thread app/src/components/settings/panels/azureDeployment.ts
Comment thread app/src/lib/i18n/id.ts
Comment thread app/src/lib/i18n/ko.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.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…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.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added bug and removed feature Net-new user-facing capability or product behavior. labels Jul 27, 2026
… 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.
@M3gA-Mind
M3gA-Mind marked this pull request as ready for review July 28, 2026 21:03
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Since the last review this PR now:

  • Extracts the duplicated Azure state machine into ai/ModelEntryField.tsx (useModelEntryMode + <ModelEntryField>), used by both pickers — your Major finding, addressed rather than deferred. The two copies had already diverged (mono on one text field only, the "select a model" placeholder option on one dropdown only, richer catalog option labels on one, three redundant source?.kind !== 'local' guards on the other); the shared version reconciles to the stricter behaviour in each case.
  • Stops a failed /models probe from blocking provider creation. That gate was the deeper bug: a provider that serves no OpenAI-shaped listing could not be created at all, so the deployment-name field was unreachable no matter how good the field was. A rejection now surfaces inline and unlocks "Add without verifying", scoped to a typed ProviderProbeError so a slug collision still blocks.
  • Adds an Azure base-URL nudge. Only /openai/v1 serves {base}/models and accepts the resource key in the authorization header (Azure's published v1 spec declares both api-key and authorization API-key schemes). That also narrows the Codex P1: bearer is not gating on the v1 base, only on the classic api-version surface.
  • Drops inference.ai.azure.com / models.ai.azure.com from the host list — those are the Foundry serverless endpoints, where model is a model name rather than a deployment name.
  • Azure-specific manual-entry label (your ko.ts point), deployment-shaped placeholder, and the sovereign-cloud hosts.

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 azureDeployment.test.ts (11), ModelEntryField.test.tsx (7, new) and the Azure/probe cases in AIPanel.test.tsx.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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.ts

Length 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"
done

Length 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 360

Length of output: 28138


@M3gA-Mind I found one remaining issue in the new probe fallback flow:

P1 — Preserve the existing credential when an edited provider’s probe fails

In app/src/components/settings/panels/AIPanel.tsx, the editor writes a replacement key before probing (Lines 3571-3576). If /models rejects, it restores the previous provider configuration (Line 3606) but then calls clearCloudProviderKey(upserted.slug) (Lines 3607-3609). Since edits retain the same slug, that removes the active credential for the restored provider too.

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 ModelEntryField extraction, endpoint classification changes, and Azure deployment-name behavior otherwise look coherent.

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Jul 28, 2026
…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.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Pushed f02a64e4d on top: a self-review catch in the shared field.

The "Loading models" placeholder gated on the effective entry mode, but the panel clears the catalog before fetching, so useManualEntry is transiently true for every provider while the probe is in flight. The dropdown-mode loading state therefore never rendered and the field flashed straight to free text. It now gates on the explicit mode: Azure still skips the wait, everyone else gets the disabled placeholder back exactly as before.

Worth noting the test I had written for that branch passed a non-empty catalog alongside catalogLoading — a combination the panel never produces — so it did not catch it. It models the real shape now.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
app/src/components/settings/panels/__tests__/azureDeployment.test.ts (1)

102-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider pinning two more isAzureV1BaseUrl shapes.

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 amber azureV1EndpointHint is 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 win

Add grep-friendly diagnostics for the mode transitions.

toggleManualEntry and syncToEndpoint are the two state transitions of this new shared flow and neither is logged, while the surrounding panel logs consistently under [ai-settings]. A one-line console.debug per 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 win

Log the probe-bypass branch.

The probe-failure path logs under [ai-settings], but taking the new skipProbe shortcut — 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6dfde and e990655.

📒 Files selected for processing (21)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/components/settings/panels/__tests__/ModelEntryField.test.tsx
  • app/src/components/settings/panels/__tests__/azureDeployment.test.ts
  • app/src/components/settings/panels/ai/ModelEntryField.tsx
  • app/src/components/settings/panels/azureDeployment.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • docs/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

Comment thread app/src/components/settings/panels/__tests__/AIPanel.test.tsx
Comment thread app/src/components/settings/panels/__tests__/AIPanel.test.tsx
Comment thread app/src/components/settings/panels/ai/ModelEntryField.tsx Outdated
Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
🧩 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.ts

Length of output: 118872


@M3gA-Mind The f02a64e4d loading fix is correct: using explicit manualEntry preserves the disabled “Loading models…” select for a non-Azure provider while its empty catalog is being populated, while Azure continues directly to the deployment-name field. The revised test now models that actual empty-catalog state. The Azure retry copy change is also correct and covered.

One previously reported issue remains:

P1 — Failed edits still erase the existing provider credential

In app/src/components/settings/panels/AIPanel.tsx, an edit stores the replacement key before probing (Line 3575). On probe failure, the code restores the old provider metadata but clears the key for that same slug (Lines 3606–3608). Since an edit retains its slug, this leaves the restored existing provider without any credential.

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 performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…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()`.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

7cf7806f6 addresses all five findings.

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 /openai/v1 base that bet is already lost, since {base}/chat/completions is not a route Azure serves there and the stored bearer auth is the wrong header. knownUnusableEndpoint now withholds both the bypass button and its explanatory hint for those URLs, leaving the inline nudge as the only way forward. The test that blessed saving .../openai is corrected to the v1 base, and a new test pins the withholding.

Minor — exercise an actual non-probe submit failure. Correct, the slug case never reached the catch. Two new cases drive a rejecting setCloudProviderKey: one asserts the bypass is absent for that failure class, the other that a prior probe failure does not leave it behind.

Minor — probeFailed never cleared. Real bug, fixed: it resets alongside submitError at the top of every attempt. Covered by the second new test above.

Minor — loading placeholder. Already shipped in f02a64e; I caught the same thing on self-review and your diagnosis matches mine exactly.

Minor — Claude Code help text hard-coded. Fair even though it predates this PR, since it sits in the diff. Now settings.ai.claudeCode.modelHelp across all 14 locales. The inline <code> fragments are dropped in favour of one translatable sentence: five interleaved markup spans do not survive translation intact, and the identifiers read fine unstyled.

Gates: typecheck clean, ESLint 0 errors, i18n:check missing 0 / extra 0, i18n:english:check 0 unexpected, no em dashes, settings-panel suite 48 files / 680 passed.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
🧩 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/src

Length 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"
done

Length 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"
done

Length of output: 52823


@M3gA-Mind One P1 remains: the failed-edit credential rollback is still destructive.

In app/src/components/settings/panels/AIPanel.tsx, Lines 3571–3573 write the replacement key before probing. When the probe fails, Lines 3603–3605 restore the prior provider metadata but clear the key for that slug. For an edit, the slug is unchanged, so this removes the just-written key—but cannot restore the original secret, which has already been overwritten. The restored provider is therefore left without its prior usable credential.

The new tests correctly cover legacy Azure bypass withholding, key-write failures, and probeFailed reset, but there is still no regression for:

edit existing provider → enter replacement key → probe fails → cancel → original provider and original credential remain usable

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 clearCloudProviderKey would preserve the replacement key, not the original one.

The Azure /openai/v1 bypass gating, explicit loading-mode fix, probe-state reset, and Claude Code localization updates otherwise look correct.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@senamakel
senamakel merged commit 8072f08 into tinyhumansai:main Jul 29, 2026
23 of 25 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[Bug] Azure Foundry: "Model not found" when deployment name differs from model name — allow free-text deployment name input

2 participants